Use short GUIDs in URLs and forms (#1)
- Fully implement single-case DUs for previously aliased IDs - Capitalize entity data items
This commit is contained in:
parent
7786896dfd
commit
f068d20612
@ -11,13 +11,13 @@ module private Helpers =
|
||||
let reqSort sort (q : IQueryable<PrayerRequest>) =
|
||||
match sort with
|
||||
| SortByDate ->
|
||||
q.OrderByDescending(fun req -> req.updatedDate)
|
||||
.ThenByDescending(fun req -> req.enteredDate)
|
||||
.ThenBy (fun req -> req.requestor)
|
||||
q.OrderByDescending(fun req -> req.UpdatedDate)
|
||||
.ThenByDescending(fun req -> req.EnteredDate)
|
||||
.ThenBy (fun req -> req.Requestor)
|
||||
| SortByRequestor ->
|
||||
q.OrderBy(fun req -> req.requestor)
|
||||
.ThenByDescending(fun req -> req.updatedDate)
|
||||
.ThenByDescending (fun req -> req.enteredDate)
|
||||
q.OrderBy(fun req -> req.Requestor)
|
||||
.ThenByDescending(fun req -> req.UpdatedDate)
|
||||
.ThenByDescending (fun req -> req.EnteredDate)
|
||||
|
||||
/// Paginate a prayer request query
|
||||
let paginate (pageNbr : int) pageSize (q : IQueryable<PrayerRequest>) =
|
||||
@ -48,44 +48,44 @@ type AppDbContext with
|
||||
(*-- CHURCH EXTENSIONS --*)
|
||||
|
||||
/// Find a church by its Id
|
||||
member this.TryChurchById cId = backgroundTask {
|
||||
let! church = this.Churches.SingleOrDefaultAsync (fun ch -> ch.churchId = cId)
|
||||
member this.TryChurchById churchId = backgroundTask {
|
||||
let! church = this.Churches.SingleOrDefaultAsync (fun ch -> ch.Id = churchId)
|
||||
return Option.fromObject church
|
||||
}
|
||||
|
||||
/// Find all churches
|
||||
member this.AllChurches () = backgroundTask {
|
||||
let! churches = this.Churches.OrderBy(fun ch -> ch.name).ToListAsync ()
|
||||
let! churches = this.Churches.OrderBy(fun ch -> ch.Name).ToListAsync ()
|
||||
return List.ofSeq churches
|
||||
}
|
||||
|
||||
(*-- MEMBER EXTENSIONS --*)
|
||||
|
||||
/// Get a small group member by its Id
|
||||
member this.TryMemberById mbrId = backgroundTask {
|
||||
let! mbr = this.Members.SingleOrDefaultAsync (fun m -> m.memberId = mbrId)
|
||||
member this.TryMemberById memberId = backgroundTask {
|
||||
let! mbr = this.Members.SingleOrDefaultAsync (fun m -> m.Id = memberId)
|
||||
return Option.fromObject mbr
|
||||
}
|
||||
|
||||
/// Find all members for a small group
|
||||
member this.AllMembersForSmallGroup gId = backgroundTask {
|
||||
member this.AllMembersForSmallGroup groupId = backgroundTask {
|
||||
let! members =
|
||||
this.Members.Where(fun mbr -> mbr.smallGroupId = gId)
|
||||
.OrderBy(fun mbr -> mbr.memberName)
|
||||
this.Members.Where(fun mbr -> mbr.SmallGroupId = groupId)
|
||||
.OrderBy(fun mbr -> mbr.Name)
|
||||
.ToListAsync ()
|
||||
return List.ofSeq members
|
||||
}
|
||||
|
||||
/// Count members for a small group
|
||||
member this.CountMembersForSmallGroup gId = backgroundTask {
|
||||
return! this.Members.CountAsync (fun m -> m.smallGroupId = gId)
|
||||
member this.CountMembersForSmallGroup groupId = backgroundTask {
|
||||
return! this.Members.CountAsync (fun m -> m.SmallGroupId = groupId)
|
||||
}
|
||||
|
||||
(*-- PRAYER REQUEST EXTENSIONS --*)
|
||||
|
||||
/// Get a prayer request by its Id
|
||||
member this.TryRequestById reqId = backgroundTask {
|
||||
let! req = this.PrayerRequests.SingleOrDefaultAsync (fun r -> r.prayerRequestId = reqId)
|
||||
let! req = this.PrayerRequests.SingleOrDefaultAsync (fun r -> r.Id = reqId)
|
||||
return Option.fromObject req
|
||||
}
|
||||
|
||||
@ -93,31 +93,31 @@ type AppDbContext with
|
||||
member this.AllRequestsForSmallGroup (grp : SmallGroup) clock listDate activeOnly pageNbr = backgroundTask {
|
||||
let theDate = match listDate with Some dt -> dt | _ -> grp.localDateNow clock
|
||||
let query =
|
||||
this.PrayerRequests.Where(fun req -> req.smallGroupId = grp.smallGroupId)
|
||||
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 = DateTime (theDate.AddDays(-(float grp.Preferences.DaysToExpire)).Date.Ticks, DateTimeKind.Utc)
|
||||
q.Where(fun req ->
|
||||
( req.updatedDate > asOf
|
||||
|| req.expiration = Manual
|
||||
|| req.requestType = LongTermRequest
|
||||
|| req.requestType = Expecting)
|
||||
&& req.expiration <> Forced)
|
||||
|> reqSort grp.preferences.requestSort
|
||||
|> paginate pageNbr grp.preferences.pageSize
|
||||
| q -> reqSort grp.preferences.requestSort q
|
||||
( req.UpdatedDate > asOf
|
||||
|| req.Expiration = Manual
|
||||
|| req.RequestType = LongTermRequest
|
||||
|| req.RequestType = Expecting)
|
||||
&& req.Expiration <> Forced)
|
||||
|> reqSort grp.Preferences.RequestSort
|
||||
|> paginate pageNbr grp.Preferences.PageSize
|
||||
| q -> reqSort grp.Preferences.RequestSort q
|
||||
let! reqs = query.ToListAsync ()
|
||||
return List.ofSeq reqs
|
||||
}
|
||||
|
||||
/// Count prayer requests for the given small group Id
|
||||
member this.CountRequestsBySmallGroup gId = backgroundTask {
|
||||
return! this.PrayerRequests.CountAsync (fun pr -> pr.smallGroupId = gId)
|
||||
member this.CountRequestsBySmallGroup groupId = backgroundTask {
|
||||
return! this.PrayerRequests.CountAsync (fun pr -> pr.SmallGroupId = groupId)
|
||||
}
|
||||
|
||||
/// Count prayer requests for the given church Id
|
||||
member this.CountRequestsByChurch cId = backgroundTask {
|
||||
return! this.PrayerRequests.CountAsync (fun pr -> pr.smallGroup.churchId = cId)
|
||||
member this.CountRequestsByChurch churchId = backgroundTask {
|
||||
return! this.PrayerRequests.CountAsync (fun pr -> pr.SmallGroup.ChurchId = churchId)
|
||||
}
|
||||
|
||||
/// Get all (or active) requests for a small group as of now or the specified date
|
||||
@ -128,9 +128,9 @@ type AppDbContext with
|
||||
SELECT * FROM pt."PrayerRequest" WHERE "SmallGroupId" = {0} AND COALESCE("Requestor", '') ILIKE {1}"""
|
||||
let like = sprintf "%%%s%%"
|
||||
let query =
|
||||
this.PrayerRequests.FromSqlRaw(sql, grp.smallGroupId, like searchTerm)
|
||||
|> reqSort grp.preferences.requestSort
|
||||
|> paginate pageNbr grp.preferences.pageSize
|
||||
this.PrayerRequests.FromSqlRaw(sql, grp.Id, like searchTerm)
|
||||
|> reqSort grp.Preferences.RequestSort
|
||||
|> paginate pageNbr grp.Preferences.PageSize
|
||||
let! reqs = query.ToListAsync ()
|
||||
return List.ofSeq reqs
|
||||
}
|
||||
@ -138,21 +138,21 @@ type AppDbContext with
|
||||
(*-- SMALL GROUP EXTENSIONS --*)
|
||||
|
||||
/// Find a small group by its Id
|
||||
member this.TryGroupById gId = backgroundTask {
|
||||
member this.TryGroupById groupId = backgroundTask {
|
||||
let! grp =
|
||||
this.SmallGroups.Include(fun sg -> sg.preferences)
|
||||
.SingleOrDefaultAsync (fun sg -> sg.smallGroupId = gId)
|
||||
this.SmallGroups.Include(fun sg -> sg.Preferences)
|
||||
.SingleOrDefaultAsync (fun sg -> sg.Id = groupId)
|
||||
return Option.fromObject grp
|
||||
}
|
||||
|
||||
/// Get small groups that are public or password protected
|
||||
member this.PublicAndProtectedGroups () = backgroundTask {
|
||||
let! groups =
|
||||
this.SmallGroups.Include(fun sg -> sg.preferences).Include(fun sg -> sg.church)
|
||||
this.SmallGroups.Include(fun sg -> sg.Preferences).Include(fun sg -> sg.Church)
|
||||
.Where(fun sg ->
|
||||
sg.preferences.isPublic
|
||||
|| (sg.preferences.groupPassword <> null && sg.preferences.groupPassword <> ""))
|
||||
.OrderBy(fun sg -> sg.church.name).ThenBy(fun sg -> sg.name)
|
||||
sg.Preferences.IsPublic
|
||||
|| (sg.Preferences.GroupPassword <> null && sg.Preferences.GroupPassword <> ""))
|
||||
.OrderBy(fun sg -> sg.Church.Name).ThenBy(fun sg -> sg.Name)
|
||||
.ToListAsync ()
|
||||
return List.ofSeq groups
|
||||
}
|
||||
@ -160,9 +160,9 @@ type AppDbContext with
|
||||
/// Get small groups that are password protected
|
||||
member this.ProtectedGroups () = backgroundTask {
|
||||
let! groups =
|
||||
this.SmallGroups.Include(fun sg -> sg.church)
|
||||
.Where(fun sg -> sg.preferences.groupPassword <> null && sg.preferences.groupPassword <> "")
|
||||
.OrderBy(fun sg -> sg.church.name).ThenBy(fun sg -> sg.name)
|
||||
this.SmallGroups.Include(fun sg -> sg.Church)
|
||||
.Where(fun sg -> sg.Preferences.GroupPassword <> null && sg.Preferences.GroupPassword <> "")
|
||||
.OrderBy(fun sg -> sg.Church.Name).ThenBy(fun sg -> sg.Name)
|
||||
.ToListAsync ()
|
||||
return List.ofSeq groups
|
||||
}
|
||||
@ -171,10 +171,10 @@ type AppDbContext with
|
||||
member this.AllGroups () = backgroundTask {
|
||||
let! groups =
|
||||
this.SmallGroups
|
||||
.Include(fun sg -> sg.church)
|
||||
.Include(fun sg -> sg.preferences)
|
||||
.Include(fun sg -> sg.preferences.timeZone)
|
||||
.OrderBy(fun sg -> sg.name)
|
||||
.Include(fun sg -> sg.Church)
|
||||
.Include(fun sg -> sg.Preferences)
|
||||
.Include(fun sg -> sg.Preferences.TimeZone)
|
||||
.OrderBy(fun sg -> sg.Name)
|
||||
.ToListAsync ()
|
||||
return List.ofSeq groups
|
||||
}
|
||||
@ -182,88 +182,89 @@ type AppDbContext with
|
||||
/// Get a small group list by their Id, with their church prepended to their name
|
||||
member this.GroupList () = backgroundTask {
|
||||
let! groups =
|
||||
this.SmallGroups.Include(fun sg -> sg.church)
|
||||
.OrderBy(fun sg -> sg.church.name).ThenBy(fun sg -> sg.name)
|
||||
this.SmallGroups.Include(fun sg -> sg.Church)
|
||||
.OrderBy(fun sg -> sg.Church.Name).ThenBy(fun sg -> sg.Name)
|
||||
.ToListAsync ()
|
||||
return groups
|
||||
|> Seq.map (fun sg -> sg.smallGroupId.ToString "N", $"{sg.church.name} | {sg.name}")
|
||||
|> List.ofSeq
|
||||
return
|
||||
groups
|
||||
|> Seq.map (fun sg -> Giraffe.ShortGuid.fromGuid sg.Id.Value, $"{sg.Church.Name} | {sg.Name}")
|
||||
|> List.ofSeq
|
||||
}
|
||||
|
||||
/// Log on a small group
|
||||
member this.TryGroupLogOnByPassword gId pw = backgroundTask {
|
||||
match! this.TryGroupById gId with
|
||||
| None -> return None
|
||||
| Some grp -> return if pw = grp.preferences.groupPassword then Some grp else None
|
||||
member this.TryGroupLogOnByPassword groupId pw = backgroundTask {
|
||||
match! this.TryGroupById groupId with
|
||||
| Some grp when pw = grp.Preferences.GroupPassword -> return Some grp
|
||||
| _ -> return None
|
||||
}
|
||||
|
||||
/// Check a cookie log on for a small group
|
||||
member this.TryGroupLogOnByCookie gId pwHash (hasher : string -> string) = backgroundTask {
|
||||
match! this.TryGroupById gId with
|
||||
member this.TryGroupLogOnByCookie groupId pwHash (hasher : string -> string) = backgroundTask {
|
||||
match! this.TryGroupById groupId with
|
||||
| None -> return None
|
||||
| Some grp -> return if pwHash = hasher grp.preferences.groupPassword then Some grp else None
|
||||
| Some grp -> return if pwHash = hasher grp.Preferences.GroupPassword then Some grp else None
|
||||
}
|
||||
|
||||
/// Count small groups for the given church Id
|
||||
member this.CountGroupsByChurch cId = backgroundTask {
|
||||
return! this.SmallGroups.CountAsync (fun sg -> sg.churchId = cId)
|
||||
member this.CountGroupsByChurch churchId = backgroundTask {
|
||||
return! this.SmallGroups.CountAsync (fun sg -> sg.ChurchId = churchId)
|
||||
}
|
||||
|
||||
(*-- TIME ZONE EXTENSIONS --*)
|
||||
|
||||
/// Get a time zone by its Id
|
||||
member this.TryTimeZoneById tzId = backgroundTask {
|
||||
let! zone = this.TimeZones.SingleOrDefaultAsync (fun tz -> tz.timeZoneId = tzId)
|
||||
let! zone = this.TimeZones.SingleOrDefaultAsync (fun tz -> tz.Id = tzId)
|
||||
return Option.fromObject zone
|
||||
}
|
||||
|
||||
/// Get all time zones
|
||||
member this.AllTimeZones () = backgroundTask {
|
||||
let! zones = this.TimeZones.OrderBy(fun tz -> tz.sortOrder).ToListAsync ()
|
||||
let! zones = this.TimeZones.OrderBy(fun tz -> tz.SortOrder).ToListAsync ()
|
||||
return List.ofSeq zones
|
||||
}
|
||||
|
||||
(*-- USER EXTENSIONS --*)
|
||||
|
||||
/// Find a user by its Id
|
||||
member this.TryUserById uId = backgroundTask {
|
||||
let! usr = this.Users.SingleOrDefaultAsync (fun u -> u.userId = uId)
|
||||
member this.TryUserById userId = backgroundTask {
|
||||
let! usr = this.Users.SingleOrDefaultAsync (fun u -> u.Id = userId)
|
||||
return Option.fromObject usr
|
||||
}
|
||||
|
||||
/// Find a user by its e-mail address and authorized small group
|
||||
member this.TryUserByEmailAndGroup email gId = backgroundTask {
|
||||
member this.TryUserByEmailAndGroup email groupId = backgroundTask {
|
||||
let! usr =
|
||||
this.Users.SingleOrDefaultAsync (fun u ->
|
||||
u.emailAddress = email && u.smallGroups.Any (fun xref -> xref.smallGroupId = gId))
|
||||
u.Email = email && u.SmallGroups.Any (fun xref -> xref.SmallGroupId = groupId))
|
||||
return Option.fromObject usr
|
||||
}
|
||||
|
||||
/// Find a user by its Id, eagerly loading the user's groups
|
||||
member this.TryUserByIdWithGroups uId = backgroundTask {
|
||||
let! usr = this.Users.Include(fun u -> u.smallGroups).SingleOrDefaultAsync (fun u -> u.userId = uId)
|
||||
member this.TryUserByIdWithGroups userId = backgroundTask {
|
||||
let! usr = this.Users.Include(fun u -> u.SmallGroups).SingleOrDefaultAsync (fun u -> u.Id = userId)
|
||||
return Option.fromObject usr
|
||||
}
|
||||
|
||||
/// Get a list of all users
|
||||
member this.AllUsers () = backgroundTask {
|
||||
let! users = this.Users.OrderBy(fun u -> u.lastName).ThenBy(fun u -> u.firstName).ToListAsync ()
|
||||
let! users = this.Users.OrderBy(fun u -> u.LastName).ThenBy(fun u -> u.FirstName).ToListAsync ()
|
||||
return List.ofSeq users
|
||||
}
|
||||
|
||||
/// Get all PrayerTracker users as members (used to send e-mails)
|
||||
member this.AllUsersAsMembers () = backgroundTask {
|
||||
let! users = this.AllUsers ()
|
||||
return users |> List.map (fun u -> { Member.empty with email = u.emailAddress; memberName = u.fullName })
|
||||
return users |> List.map (fun u -> { Member.empty with Email = u.Email; Name = u.fullName })
|
||||
}
|
||||
|
||||
/// Find a user based on their credentials
|
||||
member this.TryUserLogOnByPassword email pwHash gId = backgroundTask {
|
||||
member this.TryUserLogOnByPassword email pwHash groupId = backgroundTask {
|
||||
let! usr =
|
||||
this.Users.SingleOrDefaultAsync (fun u ->
|
||||
u.emailAddress = email
|
||||
&& u.passwordHash = pwHash
|
||||
&& u.smallGroups.Any (fun xref -> xref.smallGroupId = gId))
|
||||
u.Email = email
|
||||
&& u.PasswordHash = pwHash
|
||||
&& u.SmallGroups.Any (fun xref -> xref.SmallGroupId = groupId))
|
||||
return Option.fromObject usr
|
||||
}
|
||||
|
||||
@ -272,17 +273,17 @@ type AppDbContext with
|
||||
match! this.TryUserByIdWithGroups uId with
|
||||
| None -> return None
|
||||
| Some usr ->
|
||||
if pwHash = usr.passwordHash && usr.smallGroups |> Seq.exists (fun xref -> xref.smallGroupId = gId) then
|
||||
return Some { usr with passwordHash = ""; salt = None; smallGroups = List<UserSmallGroup>() }
|
||||
if pwHash = usr.PasswordHash && usr.SmallGroups |> Seq.exists (fun xref -> xref.SmallGroupId = gId) then
|
||||
return Some { usr with PasswordHash = ""; Salt = None; SmallGroups = List<UserSmallGroup>() }
|
||||
else return None
|
||||
}
|
||||
|
||||
/// Count the number of users for a small group
|
||||
member this.CountUsersBySmallGroup gId = backgroundTask {
|
||||
return! this.Users.CountAsync (fun u -> u.smallGroups.Any (fun xref -> xref.smallGroupId = gId))
|
||||
member this.CountUsersBySmallGroup groupId = backgroundTask {
|
||||
return! this.Users.CountAsync (fun u -> u.SmallGroups.Any (fun xref -> xref.SmallGroupId = groupId))
|
||||
}
|
||||
|
||||
/// Count the number of users for a church
|
||||
member this.CountUsersByChurch cId = backgroundTask {
|
||||
return! this.Users.CountAsync (fun u -> u.smallGroups.Any (fun xref -> xref.smallGroup.churchId = cId))
|
||||
member this.CountUsersByChurch churchId = backgroundTask {
|
||||
return! this.Users.CountAsync (fun u -> u.SmallGroups.Any (fun xref -> xref.SmallGroup.ChurchId = churchId))
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -14,6 +14,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FSharp.EFCore.OptionConverter" Version="1.0.0" />
|
||||
<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" />
|
||||
|
@ -9,13 +9,13 @@ open System
|
||||
let asOfDateDisplayTests =
|
||||
testList "AsOfDateDisplay" [
|
||||
test "NoDisplay code is correct" {
|
||||
Expect.equal NoDisplay.code "N" "The code for NoDisplay should have been \"N\""
|
||||
Expect.equal (AsOfDateDisplay.toCode NoDisplay) "N" "The code for NoDisplay should have been \"N\""
|
||||
}
|
||||
test "ShortDate code is correct" {
|
||||
Expect.equal ShortDate.code "S" "The code for ShortDate should have been \"S\""
|
||||
Expect.equal (AsOfDateDisplay.toCode ShortDate) "S" "The code for ShortDate should have been \"S\""
|
||||
}
|
||||
test "LongDate code is correct" {
|
||||
Expect.equal LongDate.code "L" "The code for LongDate should have been \"N\""
|
||||
Expect.equal (AsOfDateDisplay.toCode LongDate) "L" "The code for LongDate should have been \"N\""
|
||||
}
|
||||
test "fromCode N should return NoDisplay" {
|
||||
Expect.equal (AsOfDateDisplay.fromCode "N") NoDisplay "\"N\" should have been converted to NoDisplay"
|
||||
@ -37,14 +37,14 @@ let churchTests =
|
||||
testList "Church" [
|
||||
test "empty is as expected" {
|
||||
let mt = Church.empty
|
||||
Expect.equal mt.churchId 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"
|
||||
Expect.equal mt.st "" "The state should have been blank"
|
||||
Expect.isFalse mt.hasInterface "The church should not show that it has an interface"
|
||||
Expect.isNone mt.interfaceAddress "The interface address should not exist"
|
||||
Expect.isNotNull mt.smallGroups "The small groups navigation property should not be null"
|
||||
Expect.isEmpty mt.smallGroups "There should be no small groups for an empty church"
|
||||
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"
|
||||
Expect.equal mt.State "" "The state should have been blank"
|
||||
Expect.isFalse mt.HasInterface "The church should not show that it has an interface"
|
||||
Expect.isNone mt.InterfaceAddress "The interface address should not exist"
|
||||
Expect.isNotNull mt.SmallGroups "The small groups navigation property should not be null"
|
||||
Expect.isEmpty mt.SmallGroups "There should be no small groups for an empty church"
|
||||
}
|
||||
]
|
||||
|
||||
@ -52,10 +52,10 @@ let churchTests =
|
||||
let emailFormatTests =
|
||||
testList "EmailFormat" [
|
||||
test "HtmlFormat code is correct" {
|
||||
Expect.equal HtmlFormat.code "H" "The code for HtmlFormat should have been \"H\""
|
||||
Expect.equal (EmailFormat.toCode HtmlFormat) "H" "The code for HtmlFormat should have been \"H\""
|
||||
}
|
||||
test "PlainTextFormat code is correct" {
|
||||
Expect.equal PlainTextFormat.code "P" "The code for PlainTextFormat should have been \"P\""
|
||||
Expect.equal (EmailFormat.toCode PlainTextFormat) "P" "The code for PlainTextFormat should have been \"P\""
|
||||
}
|
||||
test "fromCode H should return HtmlFormat" {
|
||||
Expect.equal (EmailFormat.fromCode "H") HtmlFormat "\"H\" should have been converted to HtmlFormat"
|
||||
@ -74,13 +74,13 @@ let emailFormatTests =
|
||||
let expirationTests =
|
||||
testList "Expiration" [
|
||||
test "Automatic code is correct" {
|
||||
Expect.equal Automatic.code "A" "The code for Automatic should have been \"A\""
|
||||
Expect.equal (Expiration.toCode Automatic) "A" "The code for Automatic should have been \"A\""
|
||||
}
|
||||
test "Manual code is correct" {
|
||||
Expect.equal Manual.code "M" "The code for Manual should have been \"M\""
|
||||
Expect.equal (Expiration.toCode Manual) "M" "The code for Manual should have been \"M\""
|
||||
}
|
||||
test "Forced code is correct" {
|
||||
Expect.equal Forced.code "F" "The code for Forced should have been \"F\""
|
||||
Expect.equal (Expiration.toCode Forced) "F" "The code for Forced should have been \"F\""
|
||||
}
|
||||
test "fromCode A should return Automatic" {
|
||||
Expect.equal (Expiration.fromCode "A") Automatic "\"A\" should have been converted to Automatic"
|
||||
@ -102,27 +102,28 @@ let listPreferencesTests =
|
||||
testList "ListPreferences" [
|
||||
test "empty is as expected" {
|
||||
let mt = ListPreferences.empty
|
||||
Expect.equal mt.smallGroupId 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"
|
||||
Expect.equal mt.longTermUpdateWeeks 4 "The default long term update weeks should have been 4"
|
||||
Expect.equal mt.emailFromName "PrayerTracker" "The default e-mail from name should have been PrayerTracker"
|
||||
Expect.equal mt.emailFromAddress "prayer@djs-consulting.com"
|
||||
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"
|
||||
Expect.equal mt.LongTermUpdateWeeks 4 "The default long term update weeks should have been 4"
|
||||
Expect.equal mt.EmailFromName "PrayerTracker" "The default e-mail from name should have been PrayerTracker"
|
||||
Expect.equal mt.EmailFromAddress "prayer@djs-consulting.com"
|
||||
"The default e-mail from address should have been prayer@djs-consulting.com"
|
||||
Expect.equal mt.listFonts "Century Gothic,Tahoma,Luxi Sans,sans-serif"
|
||||
"The default list fonts were incorrect"
|
||||
Expect.equal mt.headingColor "maroon" "The default heading text color should have been maroon"
|
||||
Expect.equal mt.lineColor "navy" "The default heding line color should have been navy"
|
||||
Expect.equal mt.headingFontSize 16 "The default heading font size should have been 16"
|
||||
Expect.equal mt.textFontSize 12 "The default text font size should have been 12"
|
||||
Expect.equal mt.requestSort SortByDate "The default request sort should have been by date"
|
||||
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 mt.timeZoneId "America/Denver" "The default time zone should have been America/Denver"
|
||||
Expect.equal mt.timeZone.timeZoneId "" "The default preferences should have included an empty time zone"
|
||||
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"
|
||||
Expect.equal mt.Fonts "Century Gothic,Tahoma,Luxi Sans,sans-serif" "The default list fonts were incorrect"
|
||||
Expect.equal mt.HeadingColor "maroon" "The default heading text color should have been maroon"
|
||||
Expect.equal mt.LineColor "navy" "The default heding line color should have been navy"
|
||||
Expect.equal mt.HeadingFontSize 16 "The default heading font size should have been 16"
|
||||
Expect.equal mt.TextFontSize 12 "The default text font size should have been 12"
|
||||
Expect.equal mt.RequestSort SortByDate "The default request sort should have been by date"
|
||||
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 (TimeZoneId.toString mt.TimeZone.Id) ""
|
||||
"The default preferences should have included an empty time zone"
|
||||
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"
|
||||
}
|
||||
]
|
||||
|
||||
@ -131,12 +132,12 @@ let memberTests =
|
||||
testList "Member" [
|
||||
test "empty is as expected" {
|
||||
let mt = Member.empty
|
||||
Expect.equal mt.memberId Guid.Empty "The member ID should have been an empty GUID"
|
||||
Expect.equal mt.smallGroupId Guid.Empty "The small group ID should have been an empty GUID"
|
||||
Expect.equal mt.memberName "" "The member name should have been blank"
|
||||
Expect.equal mt.email "" "The member e-mail address should have been blank"
|
||||
Expect.isNone mt.format "The preferred e-mail format should not exist"
|
||||
Expect.equal mt.smallGroup.smallGroupId Guid.Empty "The small group should have been an empty one"
|
||||
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"
|
||||
Expect.equal mt.Email "" "The member e-mail address should have been blank"
|
||||
Expect.isNone mt.Format "The preferred e-mail format should not exist"
|
||||
Expect.equal mt.SmallGroup.Id.Value Guid.Empty "The small group should have been an empty one"
|
||||
}
|
||||
]
|
||||
|
||||
@ -145,62 +146,62 @@ let prayerRequestTests =
|
||||
testList "PrayerRequest" [
|
||||
test "empty is as expected" {
|
||||
let mt = PrayerRequest.empty
|
||||
Expect.equal mt.prayerRequestId 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 Guid.Empty "The user ID should have been an empty GUID"
|
||||
Expect.equal mt.smallGroupId Guid.Empty "The small group ID should have been an empty GUID"
|
||||
Expect.equal mt.enteredDate DateTime.MinValue "The entered date should have been the minimum"
|
||||
Expect.equal mt.updatedDate DateTime.MinValue "The updated date should have been the minimum"
|
||||
Expect.isNone mt.requestor "The requestor should not exist"
|
||||
Expect.equal mt.text "" "The request text should have been blank"
|
||||
Expect.isFalse mt.notifyChaplain "The notify chaplain flag should not have been set"
|
||||
Expect.equal mt.expiration Automatic "The expiration should have been Automatic"
|
||||
Expect.equal mt.user.userId Guid.Empty "The user should have been an empty one"
|
||||
Expect.equal mt.smallGroup.smallGroupId Guid.Empty "The small group should have been an empty one"
|
||||
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"
|
||||
Expect.equal mt.SmallGroupId.Value Guid.Empty "The small group ID should have been an empty GUID"
|
||||
Expect.equal mt.EnteredDate DateTime.MinValue "The entered date should have been the minimum"
|
||||
Expect.equal mt.UpdatedDate DateTime.MinValue "The updated date should have been the minimum"
|
||||
Expect.isNone mt.Requestor "The requestor should not exist"
|
||||
Expect.equal mt.Text "" "The request text should have been blank"
|
||||
Expect.isFalse mt.NotifyChaplain "The notify chaplain flag should not have been set"
|
||||
Expect.equal mt.Expiration Automatic "The expiration should have been Automatic"
|
||||
Expect.equal mt.User.Id.Value Guid.Empty "The user should have been an empty one"
|
||||
Expect.equal mt.SmallGroup.Id.Value Guid.Empty "The small group should have been an empty one"
|
||||
}
|
||||
test "isExpired always returns false for expecting requests" {
|
||||
let req = { PrayerRequest.empty with requestType = Expecting }
|
||||
let req = { PrayerRequest.empty with RequestType = Expecting }
|
||||
Expect.isFalse (req.isExpired DateTime.Now 0) "An expecting request should never be considered expired"
|
||||
}
|
||||
test "isExpired always returns false for manually-expired requests" {
|
||||
let req = { PrayerRequest.empty with updatedDate = DateTime.Now.AddMonths -1; expiration = Manual }
|
||||
let req = { PrayerRequest.empty with UpdatedDate = DateTime.Now.AddMonths -1; Expiration = Manual }
|
||||
Expect.isFalse (req.isExpired DateTime.Now 4) "A never-expired request should never be considered expired"
|
||||
}
|
||||
test "isExpired always returns false for long term/recurring requests" {
|
||||
let req = { PrayerRequest.empty with requestType = LongTermRequest }
|
||||
let req = { PrayerRequest.empty with RequestType = LongTermRequest }
|
||||
Expect.isFalse (req.isExpired DateTime.Now 0)
|
||||
"A recurring/long-term request should never be considered expired"
|
||||
}
|
||||
test "isExpired always returns true for force-expired requests" {
|
||||
let req = { PrayerRequest.empty with updatedDate = DateTime.Now; expiration = Forced }
|
||||
let req = { PrayerRequest.empty with UpdatedDate = DateTime.Now; Expiration = Forced }
|
||||
Expect.isTrue (req.isExpired DateTime.Now 5) "A force-expired request should always be considered expired"
|
||||
}
|
||||
test "isExpired returns false for non-expired requests" {
|
||||
let now = DateTime.Now
|
||||
let req = { PrayerRequest.empty with updatedDate = now.AddDays -5. }
|
||||
let req = { PrayerRequest.empty with UpdatedDate = now.AddDays -5. }
|
||||
Expect.isFalse (req.isExpired now 7) "A request updated 5 days ago should not be considered expired"
|
||||
}
|
||||
test "isExpired returns true for expired requests" {
|
||||
let now = DateTime.Now
|
||||
let req = { PrayerRequest.empty with updatedDate = now.AddDays -8. }
|
||||
let req = { PrayerRequest.empty with UpdatedDate = now.AddDays -8. }
|
||||
Expect.isTrue (req.isExpired now 7) "A request updated 8 days ago should be considered expired"
|
||||
}
|
||||
test "isExpired returns true for same-day expired requests" {
|
||||
let now = DateTime.Now
|
||||
let req = { PrayerRequest.empty with updatedDate = now.Date.AddDays(-7.).AddSeconds -1. }
|
||||
let req = { PrayerRequest.empty with UpdatedDate = now.Date.AddDays(-7.).AddSeconds -1. }
|
||||
Expect.isTrue (req.isExpired now 7)
|
||||
"A request entered a second before midnight should be considered expired"
|
||||
}
|
||||
test "updateRequired returns false for expired requests" {
|
||||
let req = { PrayerRequest.empty with expiration = Forced }
|
||||
let req = { PrayerRequest.empty with Expiration = Forced }
|
||||
Expect.isFalse (req.updateRequired DateTime.Now 7 4) "An expired request should not require an update"
|
||||
}
|
||||
test "updateRequired returns false when an update is not required for an active request" {
|
||||
let now = DateTime.Now
|
||||
let req =
|
||||
{ PrayerRequest.empty with
|
||||
requestType = LongTermRequest
|
||||
updatedDate = now.AddDays -14.
|
||||
RequestType = LongTermRequest
|
||||
UpdatedDate = now.AddDays -14.
|
||||
}
|
||||
Expect.isFalse (req.updateRequired now 7 4)
|
||||
"An active request updated 14 days ago should not require an update until 28 days"
|
||||
@ -209,8 +210,8 @@ let prayerRequestTests =
|
||||
let now = DateTime.Now
|
||||
let req =
|
||||
{ PrayerRequest.empty with
|
||||
requestType = LongTermRequest
|
||||
updatedDate = now.AddDays -34.
|
||||
RequestType = LongTermRequest
|
||||
UpdatedDate = now.AddDays -34.
|
||||
}
|
||||
Expect.isTrue (req.updateRequired now 7 4)
|
||||
"An active request updated 34 days ago should require an update (past 28 days)"
|
||||
@ -221,19 +222,21 @@ let prayerRequestTests =
|
||||
let prayerRequestTypeTests =
|
||||
testList "PrayerRequestType" [
|
||||
test "CurrentRequest code is correct" {
|
||||
Expect.equal CurrentRequest.code "C" "The code for CurrentRequest should have been \"C\""
|
||||
Expect.equal (PrayerRequestType.toCode CurrentRequest) "C"
|
||||
"The code for CurrentRequest should have been \"C\""
|
||||
}
|
||||
test "LongTermRequest code is correct" {
|
||||
Expect.equal LongTermRequest.code "L" "The code for LongTermRequest should have been \"L\""
|
||||
Expect.equal (PrayerRequestType.toCode LongTermRequest) "L"
|
||||
"The code for LongTermRequest should have been \"L\""
|
||||
}
|
||||
test "PraiseReport code is correct" {
|
||||
Expect.equal PraiseReport.code "P" "The code for PraiseReport should have been \"P\""
|
||||
Expect.equal (PrayerRequestType.toCode PraiseReport) "P" "The code for PraiseReport should have been \"P\""
|
||||
}
|
||||
test "Expecting code is correct" {
|
||||
Expect.equal Expecting.code "E" "The code for Expecting should have been \"E\""
|
||||
Expect.equal (PrayerRequestType.toCode Expecting) "E" "The code for Expecting should have been \"E\""
|
||||
}
|
||||
test "Announcement code is correct" {
|
||||
Expect.equal Announcement.code "A" "The code for Announcement should have been \"A\""
|
||||
Expect.equal (PrayerRequestType.toCode Announcement) "A" "The code for Announcement should have been \"A\""
|
||||
}
|
||||
test "fromCode C should return CurrentRequest" {
|
||||
Expect.equal (PrayerRequestType.fromCode "C") CurrentRequest
|
||||
@ -264,10 +267,10 @@ let prayerRequestTypeTests =
|
||||
let requestSortTests =
|
||||
testList "RequestSort" [
|
||||
test "SortByDate code is correct" {
|
||||
Expect.equal SortByDate.code "D" "The code for SortByDate should have been \"D\""
|
||||
Expect.equal (RequestSort.toCode SortByDate) "D" "The code for SortByDate should have been \"D\""
|
||||
}
|
||||
test "SortByRequestor code is correct" {
|
||||
Expect.equal SortByRequestor.code "R" "The code for SortByRequestor should have been \"R\""
|
||||
Expect.equal (RequestSort.toCode SortByRequestor) "R" "The code for SortByRequestor should have been \"R\""
|
||||
}
|
||||
test "fromCode D should return SortByDate" {
|
||||
Expect.equal (RequestSort.fromCode "D") SortByDate "\"D\" should have been converted to SortByDate"
|
||||
@ -290,23 +293,23 @@ let smallGroupTests =
|
||||
FakeClock (Instant.FromDateTimeUtc now) |> f
|
||||
yield test "empty is as expected" {
|
||||
let mt = SmallGroup.empty
|
||||
Expect.equal mt.smallGroupId Guid.Empty "The small group ID should have been an empty GUID"
|
||||
Expect.equal mt.churchId Guid.Empty "The church ID should have been an empty GUID"
|
||||
Expect.equal mt.name "" "The name should have been blank"
|
||||
Expect.equal mt.church.churchId Guid.Empty "The church should have been an empty one"
|
||||
Expect.isNotNull mt.members "The members navigation property should not be null"
|
||||
Expect.isEmpty mt.members "There should be no members for an empty small group"
|
||||
Expect.isNotNull mt.prayerRequests "The prayer requests navigation property should not be null"
|
||||
Expect.isEmpty mt.prayerRequests "There should be no prayer requests for an empty small group"
|
||||
Expect.isNotNull mt.users "The users navigation property should not be null"
|
||||
Expect.isEmpty mt.users "There should be no users for an empty small group"
|
||||
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"
|
||||
Expect.equal mt.Church.Id.Value Guid.Empty "The church should have been an empty one"
|
||||
Expect.isNotNull mt.Members "The members navigation property should not be null"
|
||||
Expect.isEmpty mt.Members "There should be no members for an empty small group"
|
||||
Expect.isNotNull mt.PrayerRequests "The prayer requests navigation property should not be null"
|
||||
Expect.isEmpty mt.PrayerRequests "There should be no prayer requests for an empty small group"
|
||||
Expect.isNotNull mt.Users "The users navigation property should not be null"
|
||||
Expect.isEmpty mt.Users "There should be no users for an empty small group"
|
||||
}
|
||||
yield! testFixture withFakeClock [
|
||||
"localTimeNow adjusts the time ahead of UTC",
|
||||
fun clock ->
|
||||
let grp =
|
||||
{ SmallGroup.empty with
|
||||
preferences = { ListPreferences.empty with timeZoneId = "Europe/Berlin" }
|
||||
Preferences = { ListPreferences.empty with TimeZoneId = TimeZoneId "Europe/Berlin" }
|
||||
}
|
||||
Expect.isGreaterThan (grp.localTimeNow clock) now "UTC to Europe/Berlin should have added hours"
|
||||
"localTimeNow adjusts the time behind UTC",
|
||||
@ -315,7 +318,10 @@ let smallGroupTests =
|
||||
"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 = "garbage" } }
|
||||
let grp =
|
||||
{ SmallGroup.empty with
|
||||
Preferences = { ListPreferences.empty with TimeZoneId = TimeZoneId "garbage" }
|
||||
}
|
||||
Expect.equal (grp.localTimeNow clock) now "UTC should have been returned for an invalid time zone"
|
||||
]
|
||||
yield test "localTimeNow fails when clock is not passed" {
|
||||
@ -334,10 +340,10 @@ let timeZoneTests =
|
||||
testList "TimeZone" [
|
||||
test "empty is as expected" {
|
||||
let mt = TimeZone.empty
|
||||
Expect.equal mt.timeZoneId "" "The time zone ID should have been blank"
|
||||
Expect.equal mt.description "" "The description should have been blank"
|
||||
Expect.equal mt.sortOrder 0 "The sort order should have been zero"
|
||||
Expect.isFalse mt.isActive "The is-active flag should not have been set"
|
||||
Expect.equal (TimeZoneId.toString mt.Id) "" "The time zone ID should have been blank"
|
||||
Expect.equal mt.Description "" "The description should have been blank"
|
||||
Expect.equal mt.SortOrder 0 "The sort order should have been zero"
|
||||
Expect.isFalse mt.IsActive "The is-active flag should not have been set"
|
||||
}
|
||||
]
|
||||
|
||||
@ -346,18 +352,18 @@ let userTests =
|
||||
testList "User" [
|
||||
test "empty is as expected" {
|
||||
let mt = User.empty
|
||||
Expect.equal mt.userId 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"
|
||||
Expect.equal mt.emailAddress "" "The e-mail address should have been blank"
|
||||
Expect.isFalse mt.isAdmin "The is admin flag should not have been set"
|
||||
Expect.equal mt.passwordHash "" "The password hash should have been blank"
|
||||
Expect.isNone mt.salt "The password salt should not exist"
|
||||
Expect.isNotNull mt.smallGroups "The small groups navigation property should not have been null"
|
||||
Expect.isEmpty mt.smallGroups "There should be no small groups for an empty user"
|
||||
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"
|
||||
Expect.equal mt.Email "" "The e-mail address should have been blank"
|
||||
Expect.isFalse mt.IsAdmin "The is admin flag should not have been set"
|
||||
Expect.equal mt.PasswordHash "" "The password hash should have been blank"
|
||||
Expect.isNone mt.Salt "The password salt should not exist"
|
||||
Expect.isNotNull mt.SmallGroups "The small groups navigation property should not have been null"
|
||||
Expect.isEmpty mt.SmallGroups "There should be no small groups for an empty user"
|
||||
}
|
||||
test "fullName 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.fullName "Unit Test" "The full name should be the first and last, separated by a space"
|
||||
}
|
||||
]
|
||||
@ -367,9 +373,9 @@ let userSmallGroupTests =
|
||||
testList "UserSmallGroup" [
|
||||
test "empty is as expected" {
|
||||
let mt = UserSmallGroup.empty
|
||||
Expect.equal mt.userId Guid.Empty "The user ID should have been an empty GUID"
|
||||
Expect.equal mt.smallGroupId Guid.Empty "The small group ID should have been an empty GUID"
|
||||
Expect.equal mt.user.userId Guid.Empty "The user should have been an empty one"
|
||||
Expect.equal mt.smallGroup.smallGroupId Guid.Empty "The small group should have been an empty one"
|
||||
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"
|
||||
Expect.equal mt.User.Id.Value Guid.Empty "The user should have been an empty one"
|
||||
Expect.equal mt.SmallGroup.Id.Value Guid.Empty "The small group should have been an empty one"
|
||||
}
|
||||
]
|
||||
|
@ -178,37 +178,38 @@ let tableSummaryTests =
|
||||
|
||||
module TimeZones =
|
||||
|
||||
open PrayerTracker.Entities
|
||||
open PrayerTracker.Views.CommonFunctions.TimeZones
|
||||
|
||||
[<Tests>]
|
||||
let nameTests =
|
||||
testList "TimeZones.name" [
|
||||
test "succeeds for US Eastern time" {
|
||||
Expect.equal (name "America/New_York" _s |> string) "Eastern"
|
||||
Expect.equal (name (TimeZoneId "America/New_York") _s |> string) "Eastern"
|
||||
"US Eastern time zone not returned correctly"
|
||||
}
|
||||
test "succeeds for US Central time" {
|
||||
Expect.equal (name "America/Chicago" _s |> string) "Central"
|
||||
Expect.equal (name (TimeZoneId "America/Chicago") _s |> string) "Central"
|
||||
"US Central time zone not returned correctly"
|
||||
}
|
||||
test "succeeds for US Mountain time" {
|
||||
Expect.equal (name "America/Denver" _s |> string) "Mountain"
|
||||
Expect.equal (name (TimeZoneId "America/Denver") _s |> string) "Mountain"
|
||||
"US Mountain time zone not returned correctly"
|
||||
}
|
||||
test "succeeds for US Mountain (AZ) time" {
|
||||
Expect.equal (name "America/Phoenix" _s |> string) "Mountain (Arizona)"
|
||||
Expect.equal (name (TimeZoneId "America/Phoenix") _s |> string) "Mountain (Arizona)"
|
||||
"US Mountain (AZ) time zone not returned correctly"
|
||||
}
|
||||
test "succeeds for US Pacific time" {
|
||||
Expect.equal (name "America/Los_Angeles" _s |> string) "Pacific"
|
||||
Expect.equal (name (TimeZoneId "America/Los_Angeles") _s |> string) "Pacific"
|
||||
"US Pacific time zone not returned correctly"
|
||||
}
|
||||
test "succeeds for Central European time" {
|
||||
Expect.equal (name "Europe/Berlin" _s |> string) "Central European"
|
||||
Expect.equal (name (TimeZoneId "Europe/Berlin") _s |> string) "Central European"
|
||||
"Central European time zone not returned correctly"
|
||||
}
|
||||
test "fails for unexpected time zone" {
|
||||
Expect.equal (name "Wakanda" _s |> string) "Wakanda"
|
||||
Expect.equal (name (TimeZoneId "Wakanda") _s |> string) "Wakanda"
|
||||
"Unexpected time zone should have returned the original ID"
|
||||
}
|
||||
]
|
||||
|
@ -21,9 +21,12 @@ module ReferenceListTests =
|
||||
test "has all three options listed" {
|
||||
let asOf = ReferenceList.asOfDateList _s
|
||||
Expect.hasCountOf asOf 3u countAll "There should have been 3 as-of choices returned"
|
||||
Expect.exists asOf (fun (x, _) -> x = NoDisplay.code) "The option for no display was not found"
|
||||
Expect.exists asOf (fun (x, _) -> x = ShortDate.code) "The option for a short date was not found"
|
||||
Expect.exists asOf (fun (x, _) -> x = LongDate.code) "The option for a full date was not found"
|
||||
Expect.exists asOf (fun (x, _) -> x = AsOfDateDisplay.toCode NoDisplay)
|
||||
"The option for no display was not found"
|
||||
Expect.exists asOf (fun (x, _) -> x = AsOfDateDisplay.toCode ShortDate)
|
||||
"The option for a short date was not found"
|
||||
Expect.exists asOf (fun (x, _) -> x = AsOfDateDisplay.toCode LongDate)
|
||||
"The option for a full date was not found"
|
||||
}
|
||||
]
|
||||
|
||||
@ -37,9 +40,9 @@ module ReferenceListTests =
|
||||
Expect.equal (fst top) "" "The default option should have been blank"
|
||||
Expect.equal (snd top).Value "Group Default (HTML Format)" "The default option label was incorrect"
|
||||
let nxt = typs |> Seq.skip 1 |> Seq.head
|
||||
Expect.equal (fst nxt) HtmlFormat.code "The 2nd option should have been HTML"
|
||||
Expect.equal (fst nxt) (EmailFormat.toCode HtmlFormat) "The 2nd option should have been HTML"
|
||||
let lst = typs |> Seq.last
|
||||
Expect.equal (fst lst) PlainTextFormat.code "The 3rd option should have been plain text"
|
||||
Expect.equal (fst lst) (EmailFormat.toCode PlainTextFormat) "The 3rd option should have been plain text"
|
||||
}
|
||||
]
|
||||
|
||||
@ -49,17 +52,19 @@ module ReferenceListTests =
|
||||
test "excludes immediate expiration if not required" {
|
||||
let exps = ReferenceList.expirationList _s false
|
||||
Expect.hasCountOf exps 2u countAll "There should have been 2 expiration types returned"
|
||||
Expect.exists exps (fun (exp, _) -> exp = Automatic.code)
|
||||
Expect.exists exps (fun (exp, _) -> exp = Expiration.toCode Automatic)
|
||||
"The option for automatic expiration was not found"
|
||||
Expect.exists exps (fun (exp, _) -> exp = Manual.code) "The option for manual expiration was not found"
|
||||
Expect.exists exps (fun (exp, _) -> exp = Expiration.toCode Manual)
|
||||
"The option for manual expiration was not found"
|
||||
}
|
||||
test "includes immediate expiration if required" {
|
||||
let exps = ReferenceList.expirationList _s true
|
||||
Expect.hasCountOf exps 3u countAll "There should have been 3 expiration types returned"
|
||||
Expect.exists exps (fun (exp, _) -> exp = Automatic.code)
|
||||
Expect.exists exps (fun (exp, _) -> exp = Expiration.toCode Automatic)
|
||||
"The option for automatic expiration was not found"
|
||||
Expect.exists exps (fun (exp, _) -> exp = Manual.code) "The option for manual expiration was not found"
|
||||
Expect.exists exps (fun (exp, _) -> exp = Forced.code)
|
||||
Expect.exists exps (fun (exp, _) -> exp = Expiration.toCode Manual)
|
||||
"The option for manual expiration was not found"
|
||||
Expect.exists exps (fun (exp, _) -> exp = Expiration.toCode Forced)
|
||||
"The option for immediate expiration was not found"
|
||||
}
|
||||
]
|
||||
@ -127,9 +132,9 @@ let appViewInfoTests =
|
||||
let assignGroupsTests =
|
||||
testList "AssignGroups" [
|
||||
test "fromUser populates correctly" {
|
||||
let usr = { User.empty with userId = Guid.NewGuid (); 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 usr.userId "The user ID was not filled correctly"
|
||||
Expect.equal asg.UserId (shortGuid usr.Id.Value) "The user ID was not filled correctly"
|
||||
Expect.equal asg.UserName usr.fullName "The user name was not filled correctly"
|
||||
Expect.equal asg.SmallGroups "" "The small group string was not filled correctly"
|
||||
}
|
||||
@ -141,38 +146,38 @@ let editChurchTests =
|
||||
test "fromChurch populates correctly when interface exists" {
|
||||
let church =
|
||||
{ Church.empty with
|
||||
churchId = Guid.NewGuid ()
|
||||
name = "Unit Test"
|
||||
city = "Testlandia"
|
||||
st = "UT"
|
||||
hasInterface = true
|
||||
interfaceAddress = Some "https://test-dem-units.test"
|
||||
Id = (Guid.NewGuid >> ChurchId) ()
|
||||
Name = "Unit Test"
|
||||
City = "Testlandia"
|
||||
State = "UT"
|
||||
HasInterface = true
|
||||
InterfaceAddress = Some "https://test-dem-units.test"
|
||||
}
|
||||
let edit = EditChurch.fromChurch church
|
||||
Expect.equal edit.ChurchId church.churchId "The church ID was not filled correctly"
|
||||
Expect.equal edit.Name church.name "The church name was not filled correctly"
|
||||
Expect.equal edit.City church.city "The church's city was not filled correctly"
|
||||
Expect.equal edit.State church.st "The church's state was not filled correctly"
|
||||
Expect.equal edit.ChurchId (shortGuid church.Id.Value) "The church ID was not filled correctly"
|
||||
Expect.equal edit.Name church.Name "The church name was not filled correctly"
|
||||
Expect.equal edit.City church.City "The church's city was not filled correctly"
|
||||
Expect.equal edit.State church.State "The church's state was not filled correctly"
|
||||
Expect.isSome edit.HasInterface "The church should show that it has an interface"
|
||||
Expect.equal edit.HasInterface (Some true) "The hasInterface flag should be true"
|
||||
Expect.isSome edit.InterfaceAddress "The interface address should exist"
|
||||
Expect.equal edit.InterfaceAddress church.interfaceAddress "The interface address was not filled correctly"
|
||||
Expect.equal edit.InterfaceAddress church.InterfaceAddress "The interface address was not filled correctly"
|
||||
}
|
||||
test "fromChurch populates correctly when interface does not exist" {
|
||||
let edit =
|
||||
EditChurch.fromChurch
|
||||
{ Church.empty with
|
||||
churchId = Guid.NewGuid ()
|
||||
name = "Unit Test"
|
||||
city = "Testlandia"
|
||||
st = "UT"
|
||||
Id = (Guid.NewGuid >> ChurchId) ()
|
||||
Name = "Unit Test"
|
||||
City = "Testlandia"
|
||||
State = "UT"
|
||||
}
|
||||
Expect.isNone edit.HasInterface "The church should not show that it has an interface"
|
||||
Expect.isNone edit.InterfaceAddress "The interface address should not exist"
|
||||
}
|
||||
test "empty is as expected" {
|
||||
let edit = EditChurch.empty
|
||||
Expect.equal edit.ChurchId Guid.Empty "The church ID should be the empty GUID"
|
||||
Expect.equal edit.ChurchId emptyGuid "The church ID should be the empty GUID"
|
||||
Expect.equal edit.Name "" "The church name should be blank"
|
||||
Expect.equal edit.City "" "The church's city should be blank"
|
||||
Expect.equal edit.State "" "The church's state should be blank"
|
||||
@ -183,13 +188,13 @@ let editChurchTests =
|
||||
Expect.isTrue EditChurch.empty.IsNew "An empty GUID should be flagged as a new church"
|
||||
}
|
||||
test "isNew works on an existing church" {
|
||||
Expect.isFalse { EditChurch.empty with ChurchId = Guid.NewGuid () }.IsNew
|
||||
Expect.isFalse { EditChurch.empty with ChurchId = (Guid.NewGuid >> shortGuid) () }.IsNew
|
||||
"A non-empty GUID should not be flagged as a new church"
|
||||
}
|
||||
test "populateChurch works correctly when an interface exists" {
|
||||
let edit =
|
||||
{ EditChurch.empty with
|
||||
ChurchId = Guid.NewGuid ()
|
||||
ChurchId = (Guid.NewGuid >> shortGuid) ()
|
||||
Name = "Test Baptist Church"
|
||||
City = "Testerville"
|
||||
State = "TE"
|
||||
@ -197,23 +202,23 @@ let editChurchTests =
|
||||
InterfaceAddress = Some "https://test.units"
|
||||
}
|
||||
let church = edit.PopulateChurch Church.empty
|
||||
Expect.notEqual church.churchId 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"
|
||||
Expect.equal church.st edit.State "The church's state was not updated correctly"
|
||||
Expect.isTrue church.hasInterface "The church should show that it has an interface"
|
||||
Expect.isSome church.interfaceAddress "The interface address should exist"
|
||||
Expect.equal church.interfaceAddress edit.InterfaceAddress "The interface address was not updated correctly"
|
||||
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"
|
||||
Expect.equal church.State edit.State "The church's state was not updated correctly"
|
||||
Expect.isTrue church.HasInterface "The church should show that it has an interface"
|
||||
Expect.isSome church.InterfaceAddress "The interface address should exist"
|
||||
Expect.equal church.InterfaceAddress edit.InterfaceAddress "The interface address was not updated correctly"
|
||||
}
|
||||
test "populateChurch works correctly when an interface does not exist" {
|
||||
let church =
|
||||
{ EditChurch.empty with
|
||||
Name = "Test Baptist Church"
|
||||
City = "Testerville"
|
||||
State = "TE"
|
||||
Name = "Test Baptist Church"
|
||||
City = "Testerville"
|
||||
State = "TE"
|
||||
}.PopulateChurch Church.empty
|
||||
Expect.isFalse church.hasInterface "The church should show that it has an interface"
|
||||
Expect.isNone church.interfaceAddress "The interface address should exist"
|
||||
Expect.isFalse church.HasInterface "The church should show that it has an interface"
|
||||
Expect.isNone church.InterfaceAddress "The interface address should exist"
|
||||
}
|
||||
]
|
||||
|
||||
@ -223,23 +228,23 @@ let editMemberTests =
|
||||
test "fromMember populates with group default format" {
|
||||
let mbr =
|
||||
{ Member.empty with
|
||||
memberId = Guid.NewGuid ()
|
||||
memberName = "Test Name"
|
||||
email = "test_units@example.com"
|
||||
Id = (Guid.NewGuid >> MemberId) ()
|
||||
Name = "Test Name"
|
||||
Email = "test_units@example.com"
|
||||
}
|
||||
let edit = EditMember.fromMember mbr
|
||||
Expect.equal edit.MemberId mbr.memberId "The member ID was not filled correctly"
|
||||
Expect.equal edit.Name mbr.memberName "The member name was not filled correctly"
|
||||
Expect.equal edit.Email mbr.email "The e-mail address was not filled correctly"
|
||||
Expect.equal edit.MemberId (shortGuid mbr.Id.Value) "The member ID was not filled correctly"
|
||||
Expect.equal edit.Name mbr.Name "The member name was not filled correctly"
|
||||
Expect.equal edit.Email mbr.Email "The e-mail address was not filled correctly"
|
||||
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.code }
|
||||
Expect.equal edit.Format HtmlFormat.code "The e-mail format was not filled correctly"
|
||||
let edit = EditMember.fromMember { Member.empty with Format = Some HtmlFormat }
|
||||
Expect.equal edit.Format (EmailFormat.toCode HtmlFormat) "The e-mail format was not filled correctly"
|
||||
}
|
||||
test "empty is as expected" {
|
||||
let edit = EditMember.empty
|
||||
Expect.equal edit.MemberId Guid.Empty "The member ID should have been an empty GUID"
|
||||
Expect.equal edit.MemberId emptyGuid "The member ID should have been an empty GUID"
|
||||
Expect.equal edit.Name "" "The member name should have been blank"
|
||||
Expect.equal edit.Email "" "The e-mail address should have been blank"
|
||||
Expect.equal edit.Format "" "The e-mail format should have been blank"
|
||||
@ -248,7 +253,7 @@ let editMemberTests =
|
||||
Expect.isTrue EditMember.empty.IsNew "An empty GUID should be flagged as a new member"
|
||||
}
|
||||
test "isNew works for an existing member" {
|
||||
Expect.isFalse { EditMember.empty with MemberId = Guid.NewGuid () }.IsNew
|
||||
Expect.isFalse { EditMember.empty with MemberId = (Guid.NewGuid >> shortGuid) () }.IsNew
|
||||
"A non-empty GUID should not be flagged as a new member"
|
||||
}
|
||||
]
|
||||
@ -259,45 +264,47 @@ let editPreferencesTests =
|
||||
test "fromPreferences succeeds for named colors and private list" {
|
||||
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"
|
||||
Expect.equal edit.LongTermUpdateWeeks prefs.longTermUpdateWeeks
|
||||
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"
|
||||
Expect.equal edit.LongTermUpdateWeeks prefs.LongTermUpdateWeeks
|
||||
"The weeks for update were not filled correctly"
|
||||
Expect.equal edit.RequestSort prefs.requestSort.code "The request sort was not filled correctly"
|
||||
Expect.equal edit.EmailFromName prefs.emailFromName "The e-mail from name was not filled correctly"
|
||||
Expect.equal edit.EmailFromAddress prefs.emailFromAddress "The e-mail from address was not filled correctly"
|
||||
Expect.equal edit.DefaultEmailType prefs.defaultEmailType.code
|
||||
Expect.equal edit.RequestSort (RequestSort.toCode prefs.RequestSort)
|
||||
"The request sort was not filled correctly"
|
||||
Expect.equal edit.EmailFromName prefs.EmailFromName "The e-mail from name was not filled correctly"
|
||||
Expect.equal edit.EmailFromAddress prefs.EmailFromAddress "The e-mail from address was not filled correctly"
|
||||
Expect.equal edit.DefaultEmailType (EmailFormat.toCode prefs.DefaultEmailType)
|
||||
"The default e-mail type was not filled correctly"
|
||||
Expect.equal edit.LineColorType "Name" "The heading line color type was not derived correctly"
|
||||
Expect.equal edit.LineColor prefs.lineColor "The heading line color was not filled correctly"
|
||||
Expect.equal edit.LineColor prefs.LineColor "The heading line color was not filled correctly"
|
||||
Expect.equal edit.HeadingColorType "Name" "The heading text color type was not derived correctly"
|
||||
Expect.equal edit.HeadingColor prefs.headingColor "The heading text color was not filled correctly"
|
||||
Expect.equal edit.Fonts prefs.listFonts "The list fonts were not filled correctly"
|
||||
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 prefs.timeZoneId "The time zone was not filled correctly"
|
||||
Expect.equal edit.HeadingColor prefs.HeadingColor "The heading text color was not filled correctly"
|
||||
Expect.equal edit.Fonts prefs.Fonts "The list fonts were not filled correctly"
|
||||
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.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.GroupPassword (Some prefs.GroupPassword) "The group password was not filled correctly"
|
||||
Expect.equal edit.Visibility RequestVisibility.``private``
|
||||
"The list visibility was not derived correctly"
|
||||
Expect.equal edit.PageSize prefs.pageSize "The page size was not filled correctly"
|
||||
Expect.equal edit.AsOfDate prefs.asOfDateDisplay.code "The as-of date display was not filled correctly"
|
||||
Expect.equal edit.PageSize prefs.PageSize "The page size was not filled correctly"
|
||||
Expect.equal edit.AsOfDate (AsOfDateDisplay.toCode 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"
|
||||
Expect.equal edit.LineColor prefs.LineColor "The heading line color 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.GroupPassword (Some prefs.GroupPassword) "The group password was not filled correctly"
|
||||
Expect.equal edit.Visibility RequestVisibility.passwordProtected
|
||||
"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"
|
||||
Expect.equal edit.HeadingColor prefs.HeadingColor "The heading text color was not filled correctly"
|
||||
Expect.isSome edit.GroupPassword "The group password should have been set"
|
||||
Expect.equal edit.GroupPassword (Some "") "The group password was not filled correctly"
|
||||
Expect.equal edit.Visibility RequestVisibility.``public``
|
||||
@ -310,35 +317,38 @@ let editRequestTests =
|
||||
testList "EditRequest" [
|
||||
test "empty is as expected" {
|
||||
let mt = EditRequest.empty
|
||||
Expect.equal mt.RequestId Guid.Empty "The request ID should be an empty GUID"
|
||||
Expect.equal mt.RequestType CurrentRequest.code "The request type should have been \"Current\""
|
||||
Expect.equal mt.RequestId emptyGuid "The request ID should be an empty GUID"
|
||||
Expect.equal mt.RequestType (PrayerRequestType.toCode CurrentRequest)
|
||||
"The request type should have been \"Current\""
|
||||
Expect.isNone mt.EnteredDate "The entered date should have been None"
|
||||
Expect.isNone mt.SkipDateUpdate """The "skip date update" flag should have been None"""
|
||||
Expect.isNone mt.Requestor "The requestor should have been None"
|
||||
Expect.equal mt.Expiration Automatic.code """The expiration should have been "A" (Automatic)"""
|
||||
Expect.equal mt.Expiration (Expiration.toCode Automatic)
|
||||
"""The expiration should have been "A" (Automatic)"""
|
||||
Expect.equal mt.Text "" "The text should have been blank"
|
||||
}
|
||||
test "fromRequest succeeds" {
|
||||
let req =
|
||||
{ PrayerRequest.empty with
|
||||
prayerRequestId = Guid.NewGuid ()
|
||||
requestType = CurrentRequest
|
||||
requestor = Some "Me"
|
||||
expiration = Manual
|
||||
text = "the text"
|
||||
}
|
||||
Id = (Guid.NewGuid >> PrayerRequestId) ()
|
||||
RequestType = CurrentRequest
|
||||
Requestor = Some "Me"
|
||||
Expiration = Manual
|
||||
Text = "the text"
|
||||
}
|
||||
let edit = EditRequest.fromRequest req
|
||||
Expect.equal edit.RequestId req.prayerRequestId "The request ID was not filled correctly"
|
||||
Expect.equal edit.RequestType req.requestType.code "The request type was not filled correctly"
|
||||
Expect.equal edit.Requestor req.requestor "The requestor was not filled correctly"
|
||||
Expect.equal edit.Expiration Manual.code "The expiration was not filled correctly"
|
||||
Expect.equal edit.Text req.text "The text was not filled correctly"
|
||||
Expect.equal edit.RequestId (shortGuid req.Id.Value) "The request ID was not filled correctly"
|
||||
Expect.equal edit.RequestType (PrayerRequestType.toCode req.RequestType)
|
||||
"The request type was not filled correctly"
|
||||
Expect.equal edit.Requestor req.Requestor "The requestor was not filled correctly"
|
||||
Expect.equal edit.Expiration (Expiration.toCode Manual) "The expiration was not filled correctly"
|
||||
Expect.equal edit.Text req.Text "The text was not filled correctly"
|
||||
}
|
||||
test "isNew works for a new request" {
|
||||
Expect.isTrue EditRequest.empty.IsNew "An empty GUID should be flagged as a new request"
|
||||
}
|
||||
test "isNew works for an existing request" {
|
||||
Expect.isFalse { EditRequest.empty with RequestId = Guid.NewGuid () }.IsNew
|
||||
Expect.isFalse { EditRequest.empty with RequestId = (Guid.NewGuid >> shortGuid) () }.IsNew
|
||||
"A non-empty GUID should not be flagged as a new request"
|
||||
}
|
||||
]
|
||||
@ -349,37 +359,37 @@ let editSmallGroupTests =
|
||||
test "fromGroup succeeds" {
|
||||
let grp =
|
||||
{ SmallGroup.empty with
|
||||
smallGroupId = Guid.NewGuid ()
|
||||
name = "test group"
|
||||
churchId = Guid.NewGuid ()
|
||||
Id = (Guid.NewGuid >> SmallGroupId) ()
|
||||
Name = "test group"
|
||||
ChurchId = (Guid.NewGuid >> ChurchId) ()
|
||||
}
|
||||
let edit = EditSmallGroup.fromGroup grp
|
||||
Expect.equal edit.SmallGroupId grp.smallGroupId "The small group ID was not filled correctly"
|
||||
Expect.equal edit.Name grp.name "The name was not filled correctly"
|
||||
Expect.equal edit.ChurchId grp.churchId "The church ID was not filled correctly"
|
||||
Expect.equal edit.SmallGroupId (shortGuid grp.Id.Value) "The small group ID was not filled correctly"
|
||||
Expect.equal edit.Name grp.Name "The name was not filled correctly"
|
||||
Expect.equal edit.ChurchId (shortGuid grp.ChurchId.Value) "The church ID was not filled correctly"
|
||||
}
|
||||
test "empty is as expected" {
|
||||
let mt = EditSmallGroup.empty
|
||||
Expect.equal mt.SmallGroupId Guid.Empty "The small group ID should be an empty GUID"
|
||||
Expect.equal mt.SmallGroupId emptyGuid "The small group ID should be an empty GUID"
|
||||
Expect.equal mt.Name "" "The name should be blank"
|
||||
Expect.equal mt.ChurchId Guid.Empty "The church ID should be an empty GUID"
|
||||
Expect.equal mt.ChurchId emptyGuid "The church ID should be an empty GUID"
|
||||
}
|
||||
test "isNew works for a new small group" {
|
||||
Expect.isTrue EditSmallGroup.empty.IsNew "An empty GUID should be flagged as a new small group"
|
||||
}
|
||||
test "isNew works for an existing small group" {
|
||||
Expect.isFalse { EditSmallGroup.empty with SmallGroupId = Guid.NewGuid () }.IsNew
|
||||
Expect.isFalse { EditSmallGroup.empty with SmallGroupId = (Guid.NewGuid >> shortGuid) () }.IsNew
|
||||
"A non-empty GUID should not be flagged as a new small group"
|
||||
}
|
||||
test "populateGroup succeeds" {
|
||||
let edit =
|
||||
{ EditSmallGroup.empty with
|
||||
Name = "test name"
|
||||
ChurchId = Guid.NewGuid ()
|
||||
ChurchId = (Guid.NewGuid >> shortGuid) ()
|
||||
}
|
||||
let grp = edit.populateGroup SmallGroup.empty
|
||||
Expect.equal grp.name edit.Name "The name was not populated correctly"
|
||||
Expect.equal grp.churchId edit.ChurchId "The church ID was not populated correctly"
|
||||
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"
|
||||
}
|
||||
]
|
||||
|
||||
@ -388,7 +398,7 @@ let editUserTests =
|
||||
testList "EditUser" [
|
||||
test "empty is as expected" {
|
||||
let mt = EditUser.empty
|
||||
Expect.equal mt.UserId Guid.Empty "The user ID should be an empty GUID"
|
||||
Expect.equal mt.UserId emptyGuid "The user ID should be an empty GUID"
|
||||
Expect.equal mt.FirstName "" "The first name should be blank"
|
||||
Expect.equal mt.LastName "" "The last name should be blank"
|
||||
Expect.equal mt.Email "" "The e-mail address should be blank"
|
||||
@ -399,23 +409,23 @@ let editUserTests =
|
||||
test "fromUser succeeds" {
|
||||
let usr =
|
||||
{ User.empty with
|
||||
userId = Guid.NewGuid ()
|
||||
firstName = "user"
|
||||
lastName = "test"
|
||||
emailAddress = "a@b.c"
|
||||
Id = (Guid.NewGuid >> UserId) ()
|
||||
FirstName = "user"
|
||||
LastName = "test"
|
||||
Email = "a@b.c"
|
||||
}
|
||||
let edit = EditUser.fromUser usr
|
||||
Expect.equal edit.UserId usr.userId "The user ID was not filled correctly"
|
||||
Expect.equal edit.FirstName usr.firstName "The first name was not filled correctly"
|
||||
Expect.equal edit.LastName usr.lastName "The last name was not filled correctly"
|
||||
Expect.equal edit.Email usr.emailAddress "The e-mail address was not filled correctly"
|
||||
Expect.equal edit.UserId (shortGuid usr.Id.Value) "The user ID was not filled correctly"
|
||||
Expect.equal edit.FirstName usr.FirstName "The first name was not filled correctly"
|
||||
Expect.equal edit.LastName usr.LastName "The last name was not filled correctly"
|
||||
Expect.equal edit.Email usr.Email "The e-mail address was not filled correctly"
|
||||
Expect.isNone edit.IsAdmin "The IsAdmin flag was not filled correctly"
|
||||
}
|
||||
test "isNew works for a new user" {
|
||||
Expect.isTrue EditUser.empty.IsNew "An empty GUID should be flagged as a new user"
|
||||
}
|
||||
test "isNew works for an existing user" {
|
||||
Expect.isFalse { EditUser.empty with UserId = Guid.NewGuid () }.IsNew
|
||||
Expect.isFalse { EditUser.empty with UserId = (Guid.NewGuid >> shortGuid) () }.IsNew
|
||||
"A non-empty GUID should not be flagged as a new user"
|
||||
}
|
||||
test "populateUser succeeds" {
|
||||
@ -429,11 +439,11 @@ let editUserTests =
|
||||
}
|
||||
let hasher = fun x -> x + "+"
|
||||
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.emailAddress edit.Email "The e-mail address was not populated correctly"
|
||||
Expect.isTrue usr.isAdmin "The isAdmin flag was not populated correctly"
|
||||
Expect.equal usr.passwordHash (hasher edit.Password) "The password hash was not populated correctly"
|
||||
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"
|
||||
Expect.isTrue usr.IsAdmin "The isAdmin flag was not populated correctly"
|
||||
Expect.equal usr.PasswordHash (hasher edit.Password) "The password hash was not populated correctly"
|
||||
}
|
||||
]
|
||||
|
||||
@ -442,7 +452,7 @@ let groupLogOnTests =
|
||||
testList "GroupLogOn" [
|
||||
test "empty is as expected" {
|
||||
let mt = GroupLogOn.empty
|
||||
Expect.equal mt.SmallGroupId Guid.Empty "The small group ID should be an empty GUID"
|
||||
Expect.equal mt.SmallGroupId emptyGuid "The small group ID should be an empty GUID"
|
||||
Expect.equal mt.Password "" "The password should be blank"
|
||||
Expect.isNone mt.RememberMe "Remember Me should be None"
|
||||
}
|
||||
@ -454,7 +464,7 @@ let maintainRequestsTests =
|
||||
test "empty is as expected" {
|
||||
let mt = MaintainRequests.empty
|
||||
Expect.isEmpty mt.Requests "The requests for the model should have been empty"
|
||||
Expect.equal mt.SmallGroup.smallGroupId Guid.Empty "The small group should have been an empty one"
|
||||
Expect.equal mt.SmallGroup.Id.Value Guid.Empty "The small group should have been an empty one"
|
||||
Expect.isNone mt.OnlyActive "The only active flag should have been None"
|
||||
Expect.isNone mt.SearchTerm "The search term should have been None"
|
||||
Expect.isNone mt.PageNbr "The page number should have been None"
|
||||
@ -490,21 +500,21 @@ let requestListTests =
|
||||
let withRequestList f () =
|
||||
{ Requests = [
|
||||
{ PrayerRequest.empty with
|
||||
requestType = CurrentRequest
|
||||
requestor = Some "Zeb"
|
||||
text = "zyx"
|
||||
updatedDate = DateTime.Today
|
||||
RequestType = CurrentRequest
|
||||
Requestor = Some "Zeb"
|
||||
Text = "zyx"
|
||||
UpdatedDate = DateTime.Today
|
||||
}
|
||||
{ PrayerRequest.empty with
|
||||
requestType = CurrentRequest
|
||||
requestor = Some "Aaron"
|
||||
text = "abc"
|
||||
updatedDate = DateTime.Today - TimeSpan.FromDays 9.
|
||||
RequestType = CurrentRequest
|
||||
Requestor = Some "Aaron"
|
||||
Text = "abc"
|
||||
UpdatedDate = DateTime.Today - TimeSpan.FromDays 9.
|
||||
}
|
||||
{ PrayerRequest.empty with
|
||||
requestType = PraiseReport
|
||||
text = "nmo"
|
||||
updatedDate = DateTime.Today
|
||||
RequestType = PraiseReport
|
||||
Text = "nmo"
|
||||
UpdatedDate = DateTime.Today
|
||||
}
|
||||
]
|
||||
Date = DateTime.Today
|
||||
@ -517,7 +527,7 @@ let requestListTests =
|
||||
yield! testFixture withRequestList [
|
||||
"AsHtml succeeds without header or as-of date",
|
||||
fun reqList ->
|
||||
let htmlList = { reqList with SmallGroup = { reqList.SmallGroup with name = "Test HTML Group" } }
|
||||
let htmlList = { reqList with SmallGroup = { reqList.SmallGroup with Name = "Test HTML Group" } }
|
||||
let html = htmlList.AsHtml _s
|
||||
Expect.equal -1 (html.IndexOf "Test HTML Group")
|
||||
"The small group name should not have existed (no header)"
|
||||
@ -557,7 +567,7 @@ let requestListTests =
|
||||
fun reqList ->
|
||||
let htmlList =
|
||||
{ reqList with
|
||||
SmallGroup = { reqList.SmallGroup with name = "Test HTML Group" }
|
||||
SmallGroup = { reqList.SmallGroup with Name = "Test HTML Group" }
|
||||
ShowHeader = true
|
||||
}
|
||||
let html = htmlList.AsHtml _s
|
||||
@ -578,12 +588,12 @@ let requestListTests =
|
||||
{ reqList with
|
||||
SmallGroup =
|
||||
{ reqList.SmallGroup with
|
||||
preferences = { reqList.SmallGroup.preferences with asOfDateDisplay = ShortDate }
|
||||
Preferences = { reqList.SmallGroup.Preferences with AsOfDateDisplay = ShortDate }
|
||||
}
|
||||
}
|
||||
let html = htmlList.AsHtml _s
|
||||
let expected =
|
||||
htmlList.Requests[0].updatedDate.ToShortDateString ()
|
||||
htmlList.Requests[0].UpdatedDate.ToShortDateString ()
|
||||
|> sprintf """<strong>Zeb</strong> — zyx<i style="font-size:9.60pt"> (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"
|
||||
@ -593,20 +603,20 @@ let requestListTests =
|
||||
{ reqList with
|
||||
SmallGroup =
|
||||
{ reqList.SmallGroup with
|
||||
preferences = { reqList.SmallGroup.preferences with asOfDateDisplay = LongDate }
|
||||
Preferences = { reqList.SmallGroup.Preferences with AsOfDateDisplay = LongDate }
|
||||
}
|
||||
}
|
||||
let html = htmlList.AsHtml _s
|
||||
let expected =
|
||||
htmlList.Requests[0].updatedDate.ToLongDateString ()
|
||||
htmlList.Requests[0].UpdatedDate.ToLongDateString ()
|
||||
|> sprintf """<strong>Zeb</strong> — zyx<i style="font-size:9.60pt"> (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"
|
||||
"AsText succeeds with no as-of date",
|
||||
fun reqList ->
|
||||
let textList = { reqList with SmallGroup = { reqList.SmallGroup with name = "Test Group" } }
|
||||
let textList = { reqList with SmallGroup = { reqList.SmallGroup with Name = "Test Group" } }
|
||||
let text = textList.AsText _s
|
||||
Expect.stringContains text $"{textList.SmallGroup.name}\n" "Small group name not found"
|
||||
Expect.stringContains text $"{textList.SmallGroup.Name}\n" "Small group name not found"
|
||||
Expect.stringContains text "Prayer Requests\n" "List heading not found"
|
||||
Expect.stringContains text ((textList.Date.ToString "MMMM d, yyyy") + "\n \n") "List date not found"
|
||||
Expect.stringContains text "--------------------\n CURRENT REQUESTS\n--------------------\n"
|
||||
@ -623,12 +633,12 @@ let requestListTests =
|
||||
{ reqList with
|
||||
SmallGroup =
|
||||
{ reqList.SmallGroup with
|
||||
preferences = { reqList.SmallGroup.preferences with asOfDateDisplay = ShortDate }
|
||||
Preferences = { reqList.SmallGroup.Preferences with AsOfDateDisplay = ShortDate }
|
||||
}
|
||||
}
|
||||
let text = textList.AsText _s
|
||||
let expected =
|
||||
textList.Requests[0].updatedDate.ToShortDateString ()
|
||||
textList.Requests[0].UpdatedDate.ToShortDateString ()
|
||||
|> 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"
|
||||
@ -638,12 +648,12 @@ let requestListTests =
|
||||
{ reqList with
|
||||
SmallGroup =
|
||||
{ reqList.SmallGroup with
|
||||
preferences = { reqList.SmallGroup.preferences with asOfDateDisplay = LongDate }
|
||||
Preferences = { reqList.SmallGroup.Preferences with AsOfDateDisplay = LongDate }
|
||||
}
|
||||
}
|
||||
let text = textList.AsText _s
|
||||
let expected =
|
||||
textList.Requests[0].updatedDate.ToLongDateString ()
|
||||
textList.Requests[0].UpdatedDate.ToLongDateString ()
|
||||
|> 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"
|
||||
@ -663,7 +673,7 @@ let requestListTests =
|
||||
let _, _, reqs = Option.get maybeCurrent
|
||||
Expect.hasCountOf reqs 2u countAll "There should have been two requests"
|
||||
let first = List.head reqs
|
||||
Expect.equal first.text "zyx" "The requests should be sorted by updated date descending"
|
||||
Expect.equal first.Text "zyx" "The requests should be sorted by updated date descending"
|
||||
Expect.isTrue (allReqs |> List.exists (fun (typ, _, _) -> typ = PraiseReport))
|
||||
"There should have been praise reports"
|
||||
Expect.isFalse (allReqs |> List.exists (fun (typ, _, _) -> typ = Announcement))
|
||||
@ -674,14 +684,14 @@ let requestListTests =
|
||||
{ reqList with
|
||||
SmallGroup =
|
||||
{ reqList.SmallGroup with
|
||||
preferences = { reqList.SmallGroup.preferences with requestSort = SortByRequestor }
|
||||
Preferences = { reqList.SmallGroup.Preferences with RequestSort = SortByRequestor }
|
||||
}
|
||||
}
|
||||
let allReqs = newList.RequestsByType _s
|
||||
let _, _, reqs = allReqs |> List.find (fun (typ, _, _) -> typ = CurrentRequest)
|
||||
Expect.hasCountOf reqs 2u countAll "There should have been two requests"
|
||||
let first = List.head reqs
|
||||
Expect.equal first.text "abc" "The requests should be sorted by requestor"
|
||||
Expect.equal first.Text "abc" "The requests should be sorted by requestor"
|
||||
]
|
||||
]
|
||||
|
||||
@ -692,7 +702,7 @@ let userLogOnTests =
|
||||
let mt = UserLogOn.empty
|
||||
Expect.equal mt.Email "" "The e-mail address should be blank"
|
||||
Expect.equal mt.Password "" "The password should be blank"
|
||||
Expect.equal mt.SmallGroupId Guid.Empty "The small group ID should be an empty GUID"
|
||||
Expect.equal mt.SmallGroupId emptyGuid "The small group ID should be an empty GUID"
|
||||
Expect.isNone mt.RememberMe "Remember Me should be None"
|
||||
Expect.isNone mt.RedirectUrl "Redirect URL should be None"
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
module PrayerTracker.Views.Church
|
||||
|
||||
open Giraffe.ViewEngine
|
||||
open PrayerTracker
|
||||
open PrayerTracker.Entities
|
||||
open PrayerTracker.ViewModels
|
||||
|
||||
@ -19,7 +20,7 @@ let edit (model : EditChurch) ctx viewInfo =
|
||||
|> AppViewInfo.withOnLoadScript "PT.church.edit.onPageLoad"
|
||||
form [ _action "/church/save"; _method "post"; _class "pt-center-columns"; Target.content ] [
|
||||
csrfToken ctx
|
||||
input [ _type "hidden"; _name (nameof model.ChurchId); _value (flatGuid model.ChurchId) ]
|
||||
input [ _type "hidden"; _name (nameof model.ChurchId); _value model.ChurchId ]
|
||||
div [ _fieldRow ] [
|
||||
div [ _inputField ] [
|
||||
label [ _for (nameof model.Name) ] [ locStr s["Church Name"] ]
|
||||
@ -65,10 +66,10 @@ let maintain (churches : Church list) (stats : Map<string, ChurchStats>) ctx vi
|
||||
tableHeadings s [ "Actions"; "Name"; "Location"; "Groups"; "Requests"; "Users"; "Interface?" ]
|
||||
churches
|
||||
|> List.map (fun ch ->
|
||||
let chId = flatGuid ch.churchId
|
||||
let chId = shortGuid ch.Id.Value
|
||||
let delAction = $"/church/{chId}/delete"
|
||||
let delPrompt = s["Are you sure you want to delete this {0}? This action cannot be undone.",
|
||||
$"""{s["Church"].Value.ToLower ()} ({ch.name})"""]
|
||||
$"""{s["Church"].Value.ToLower ()} ({ch.Name})"""]
|
||||
tr [] [
|
||||
td [] [
|
||||
a [ _href $"/church/{chId}/edit"; _title s["Edit This Church"].Value ] [ icon "edit" ]
|
||||
@ -78,12 +79,12 @@ let maintain (churches : Church list) (stats : Map<string, ChurchStats>) ctx vi
|
||||
icon "delete_forever"
|
||||
]
|
||||
]
|
||||
td [] [ str ch.name ]
|
||||
td [] [ str ch.city; rawText ", "; str ch.st ]
|
||||
td [ _class "pt-right-text" ] [ rawText (stats[chId].smallGroups.ToString "N0") ]
|
||||
td [ _class "pt-right-text" ] [ rawText (stats[chId].prayerRequests.ToString "N0") ]
|
||||
td [ _class "pt-right-text" ] [ rawText (stats[chId].users.ToString "N0") ]
|
||||
td [ _class "pt-center-text" ] [ locStr s[if ch.hasInterface then "Yes" else "No"] ]
|
||||
td [] [ str ch.Name ]
|
||||
td [] [ str ch.City; rawText ", "; str ch.State ]
|
||||
td [ _class "pt-right-text" ] [ rawText (stats[chId].SmallGroups.ToString "N0") ]
|
||||
td [ _class "pt-right-text" ] [ rawText (stats[chId].PrayerRequests.ToString "N0") ]
|
||||
td [ _class "pt-right-text" ] [ rawText (stats[chId].Users.ToString "N0") ]
|
||||
td [ _class "pt-center-text" ] [ locStr s[if ch.HasInterface then "Yes" else "No"] ]
|
||||
])
|
||||
|> tbody []
|
||||
]
|
||||
|
@ -103,16 +103,6 @@ let selectDefault text = $"— %s{text} —"
|
||||
/// Generate a standard submit button with icon and text
|
||||
let submit attrs ico text = button (_type "submit" :: attrs) [ icon ico; rawText " "; locStr text ]
|
||||
|
||||
|
||||
open System
|
||||
|
||||
// TODO: this is where to implement issue #1
|
||||
/// Format a GUID with no dashes (used for URLs and forms)
|
||||
let flatGuid (x : Guid) = x.ToString "N"
|
||||
|
||||
/// An empty GUID string (used for "add" actions)
|
||||
let emptyGuid = flatGuid Guid.Empty
|
||||
|
||||
/// Create an HTML onsubmit event handler
|
||||
let _onsubmit = attr "onsubmit"
|
||||
|
||||
@ -167,6 +157,7 @@ let renderHtmlString = renderHtmlNode >> HtmlString
|
||||
module TimeZones =
|
||||
|
||||
open System.Collections.Generic
|
||||
open PrayerTracker.Entities
|
||||
|
||||
/// Cross-reference between time zone Ids and their English names
|
||||
let private xref =
|
||||
@ -180,7 +171,8 @@ module TimeZones =
|
||||
|> Map.ofList
|
||||
|
||||
/// Get the name of a time zone, given its Id
|
||||
let name tzId (s : IStringLocalizer) =
|
||||
let name timeZoneId (s : IStringLocalizer) =
|
||||
let tzId = TimeZoneId.toString timeZoneId
|
||||
try s[xref[tzId]]
|
||||
with :? KeyNotFoundException -> LocalizedString (tzId, tzId)
|
||||
|
||||
|
@ -50,7 +50,7 @@ module Navigation =
|
||||
]
|
||||
]
|
||||
]
|
||||
if u.isAdmin then
|
||||
if u.IsAdmin then
|
||||
li [ _class "dropdown" ] [
|
||||
a [ _dropdown
|
||||
_ariaLabel s["Administration"].Value
|
||||
@ -167,8 +167,8 @@ module Navigation =
|
||||
icon "group"
|
||||
space
|
||||
match m.User with
|
||||
| Some _ -> a [ _href "/small-group"; Target.content ] [ strong [] [ str g.name ] ]
|
||||
| None -> strong [] [ str g.name ]
|
||||
| Some _ -> a [ _href "/small-group"; Target.content ] [ strong [] [ str g.Name ] ]
|
||||
| None -> strong [] [ str g.Name ]
|
||||
rawText " "
|
||||
]
|
||||
| None -> []
|
||||
@ -297,7 +297,7 @@ let private contentSection viewInfo pgTitle (content : XmlNode) = [
|
||||
yield! messages viewInfo
|
||||
match viewInfo.ScopedStyle with
|
||||
| [] -> ()
|
||||
| styles -> style [ _scoped ] (styles |> List.map (fun it -> rawText $"{it};"))
|
||||
| styles -> style [ _scoped ] (styles |> List.map (fun it -> rawText $"{it}; "))
|
||||
content
|
||||
htmlFooter viewInfo
|
||||
for jsFile in viewInfo.Script do
|
||||
|
@ -17,13 +17,13 @@ let edit (model : EditRequest) today ctx viewInfo =
|
||||
let vi = AppViewInfo.withOnLoadScript "PT.initCKEditor" viewInfo
|
||||
form [ _action "/prayer-request/save"; _method "post"; _class "pt-center-columns"; Target.content ] [
|
||||
csrfToken ctx
|
||||
inputField "hidden" (nameof model.RequestId) (flatGuid model.RequestId) []
|
||||
inputField "hidden" (nameof model.RequestId) model.RequestId []
|
||||
div [ _fieldRow ] [
|
||||
div [ _inputField ] [
|
||||
label [ _for (nameof model.RequestType) ] [ locStr s["Request Type"] ]
|
||||
ReferenceList.requestTypeList s
|
||||
|> Seq.ofList
|
||||
|> Seq.map (fun (typ, desc) -> typ.code, desc.Value)
|
||||
|> Seq.map (fun (typ, desc) -> PrayerRequestType.toCode typ, desc.Value)
|
||||
|> selectList (nameof model.RequestType) model.RequestType [ _required; _autofocus ]
|
||||
]
|
||||
div [ _inputField ] [
|
||||
@ -76,10 +76,10 @@ let edit (model : EditRequest) today ctx viewInfo =
|
||||
/// View for the request e-mail results page
|
||||
let email model viewInfo =
|
||||
let s = I18N.localizer.Force ()
|
||||
let pageTitle = $"""{s["Prayer Requests"].Value} • {model.SmallGroup.name}"""
|
||||
let prefs = model.SmallGroup.preferences
|
||||
let addresses = model.Recipients |> List.map (fun mbr -> $"{mbr.memberName} <{mbr.email}>") |> String.concat ", "
|
||||
[ p [ _style $"font-family:{prefs.listFonts};font-size:%i{prefs.textFontSize}pt;" ] [
|
||||
let pageTitle = $"""{s["Prayer Requests"].Value} • {model.SmallGroup.Name}"""
|
||||
let prefs = model.SmallGroup.Preferences
|
||||
let addresses = model.Recipients |> List.map (fun mbr -> $"{mbr.Name} <{mbr.Email}>") |> String.concat ", "
|
||||
[ p [ _style $"font-family:{prefs.Fonts};font-size:%i{prefs.TextFontSize}pt;" ] [
|
||||
locStr s["The request list was sent to the following people, via individual e-mails"]
|
||||
rawText ":"
|
||||
br []
|
||||
@ -126,9 +126,9 @@ let lists (groups : SmallGroup list) viewInfo =
|
||||
tableHeadings s [ "Actions"; "Church"; "Group" ]
|
||||
groups
|
||||
|> List.map (fun grp ->
|
||||
let grpId = flatGuid grp.smallGroupId
|
||||
let grpId = shortGuid grp.Id.Value
|
||||
tr [] [
|
||||
if grp.preferences.isPublic then
|
||||
if grp.Preferences.IsPublic then
|
||||
a [ _href $"/prayer-requests/{grpId}/list"; _title s["View"].Value ] [ icon "list" ]
|
||||
else
|
||||
a [ _href $"/small-group/log-on/{grpId}"; _title s["Log On"].Value ] [
|
||||
@ -136,8 +136,8 @@ let lists (groups : SmallGroup list) viewInfo =
|
||||
]
|
||||
|> List.singleton
|
||||
|> td []
|
||||
td [] [ str grp.church.name ]
|
||||
td [] [ str grp.name ]
|
||||
td [] [ str grp.Church.Name ]
|
||||
td [] [ str grp.Name ]
|
||||
])
|
||||
|> tbody []
|
||||
]
|
||||
@ -153,19 +153,19 @@ let maintain (model : MaintainRequests) (ctx : HttpContext) viewInfo =
|
||||
use sw = new StringWriter ()
|
||||
let raw = rawLocText sw
|
||||
let now = model.SmallGroup.localDateNow (ctx.GetService<IClock> ())
|
||||
let prefs = model.SmallGroup.preferences
|
||||
let prefs = model.SmallGroup.Preferences
|
||||
let types = ReferenceList.requestTypeList s |> Map.ofList
|
||||
let updReq (req : PrayerRequest) =
|
||||
if req.updateRequired now prefs.daysToExpire prefs.longTermUpdateWeeks then "pt-request-update" else ""
|
||||
if req.updateRequired now prefs.DaysToExpire prefs.LongTermUpdateWeeks then "pt-request-update" else ""
|
||||
|> _class
|
||||
let reqExp (req : PrayerRequest) =
|
||||
_class (if req.isExpired now prefs.daysToExpire then "pt-request-expired" else "")
|
||||
_class (if req.isExpired now prefs.DaysToExpire then "pt-request-expired" else "")
|
||||
/// 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 = flatGuid req.prayerRequestId
|
||||
let reqText = htmlToPlainText req.text
|
||||
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.",
|
||||
@ -180,7 +180,7 @@ let maintain (model : MaintainRequests) (ctx : HttpContext) viewInfo =
|
||||
a [ _href $"/prayer-request/{reqId}/edit"; _title l["Edit This Prayer Request"].Value ] [
|
||||
icon "edit"
|
||||
]
|
||||
if req.isExpired now prefs.daysToExpire then
|
||||
if req.isExpired now prefs.DaysToExpire then
|
||||
a [ _href $"/prayer-request/{reqId}/restore"
|
||||
_title l["Restore This Inactive Request"].Value ] [
|
||||
icon "visibility"
|
||||
@ -197,10 +197,10 @@ let maintain (model : MaintainRequests) (ctx : HttpContext) viewInfo =
|
||||
]
|
||||
]
|
||||
td [ updReq req ] [
|
||||
str (req.updatedDate.ToString(s["MMMM d, yyyy"].Value, Globalization.CultureInfo.CurrentUICulture))
|
||||
str (req.UpdatedDate.ToString(s["MMMM d, yyyy"].Value, Globalization.CultureInfo.CurrentUICulture))
|
||||
]
|
||||
td [] [ locStr types[req.requestType] ]
|
||||
td [ reqExp req ] [ str (match req.requestor with Some r -> r | None -> " ") ]
|
||||
td [] [ locStr types[req.RequestType] ]
|
||||
td [ reqExp req ] [ str (match req.Requestor with Some r -> r | None -> " ") ]
|
||||
td [] [
|
||||
match reqText.Length with
|
||||
| len when len < 60 -> rawText reqText
|
||||
@ -265,7 +265,7 @@ let maintain (model : MaintainRequests) (ctx : HttpContext) viewInfo =
|
||||
let withPage = match pg with 2 -> search | _ -> ("page", string (pg - 1)) :: search
|
||||
a [ _href (makeUrl url withPage) ] [ icon "keyboard_arrow_left"; space; raw l["Previous Page"] ]
|
||||
rawText " "
|
||||
match requests.Length = model.SmallGroup.preferences.pageSize with
|
||||
match requests.Length = model.SmallGroup.Preferences.PageSize with
|
||||
| true ->
|
||||
a [ _href (makeUrl url (("page", string (pg + 1)) :: search)) ] [
|
||||
raw l["Next Page"]; space; icon "keyboard_arrow_right"
|
||||
@ -281,13 +281,13 @@ let maintain (model : MaintainRequests) (ctx : HttpContext) viewInfo =
|
||||
/// View for the printable prayer request list
|
||||
let print model version =
|
||||
let s = I18N.localizer.Force ()
|
||||
let pageTitle = $"""{s["Prayer Requests"].Value} • {model.SmallGroup.name}"""
|
||||
let pageTitle = $"""{s["Prayer Requests"].Value} • {model.SmallGroup.Name}"""
|
||||
let imgAlt = $"""{s["PrayerTracker"].Value} {s["from Bit Badger Solutions"].Value}"""
|
||||
article [] [
|
||||
rawText (model.AsHtml s)
|
||||
br []
|
||||
hr []
|
||||
div [ _style $"font-size:70%%;font-family:{model.SmallGroup.preferences.listFonts};" ] [
|
||||
div [ _style $"font-size:70%%;font-family:{model.SmallGroup.Preferences.Fonts};" ] [
|
||||
img [ _src $"""/img/{s["footer_en"].Value}.png"""
|
||||
_style "vertical-align:text-bottom;"
|
||||
_alt imgAlt
|
||||
@ -302,7 +302,7 @@ let print model version =
|
||||
/// View for the prayer request list
|
||||
let view model viewInfo =
|
||||
let s = I18N.localizer.Force ()
|
||||
let pageTitle = $"""{s["Prayer Requests"].Value} • {model.SmallGroup.name}"""
|
||||
let pageTitle = $"""{s["Prayer Requests"].Value} • {model.SmallGroup.Name}"""
|
||||
let spacer = rawText " "
|
||||
let dtString = model.Date.ToString "yyyy-MM-dd"
|
||||
div [ _class "pt-center-text" ] [
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
open Giraffe.ViewEngine
|
||||
open Microsoft.Extensions.Localization
|
||||
open PrayerTracker
|
||||
open PrayerTracker.Entities
|
||||
open PrayerTracker.ViewModels
|
||||
|
||||
@ -45,8 +46,8 @@ let announcement isAdmin ctx viewInfo =
|
||||
label [ _for (nameof model.RequestType) ] [ locStr s["Request Type"] ]
|
||||
reqTypes
|
||||
|> Seq.ofList
|
||||
|> Seq.map (fun (typ, desc) -> typ.code, desc.Value)
|
||||
|> selectList (nameof model.RequestType) Announcement.code []
|
||||
|> Seq.map (fun (typ, desc) -> PrayerRequestType.toCode typ, desc.Value)
|
||||
|> selectList (nameof model.RequestType) (PrayerRequestType.toCode Announcement) []
|
||||
]
|
||||
]
|
||||
div [ _fieldRow ] [ submit [] "send" s["Send Announcement"] ]
|
||||
@ -76,7 +77,7 @@ let edit (model : EditSmallGroup) (churches : Church list) ctx viewInfo =
|
||||
let pageTitle = if model.IsNew then "Add a New Group" else "Edit Group"
|
||||
form [ _action "/small-group/save"; _method "post"; _class "pt-center-columns"; Target.content ] [
|
||||
csrfToken ctx
|
||||
inputField "hidden" (nameof model.SmallGroupId) (flatGuid model.SmallGroupId) []
|
||||
inputField "hidden" (nameof model.SmallGroupId) model.SmallGroupId []
|
||||
div [ _fieldRow ] [
|
||||
div [ _inputField ] [
|
||||
label [ _for (nameof model.Name) ] [ locStr s["Group Name"] ]
|
||||
@ -88,9 +89,9 @@ let edit (model : EditSmallGroup) (churches : Church list) ctx viewInfo =
|
||||
label [ _for (nameof model.ChurchId) ] [ locStr s["Church"] ]
|
||||
seq {
|
||||
"", selectDefault s["Select Church"].Value
|
||||
yield! churches |> List.map (fun c -> flatGuid c.churchId, c.name)
|
||||
yield! churches |> List.map (fun c -> shortGuid c.Id.Value, c.Name)
|
||||
}
|
||||
|> selectList (nameof model.ChurchId) (flatGuid model.ChurchId) [ _required ]
|
||||
|> selectList (nameof model.ChurchId) model.ChurchId [ _required ]
|
||||
]
|
||||
]
|
||||
div [ _fieldRow ] [ submit [] "save" s["Save Group"] ]
|
||||
@ -111,7 +112,7 @@ let editMember (model : EditMember) (types : (string * LocalizedString) seq) ctx
|
||||
] viewInfo
|
||||
form [ _action "/small-group/member/save"; _method "post"; _class "pt-center-columns"; Target.content ] [
|
||||
csrfToken ctx
|
||||
inputField "hidden" (nameof model.MemberId) (flatGuid model.MemberId) []
|
||||
inputField "hidden" (nameof model.MemberId) model.MemberId []
|
||||
div [ _fieldRow ] [
|
||||
div [ _inputField ] [
|
||||
label [ _for (nameof model.Name) ] [ locStr s["Member Name"] ]
|
||||
@ -140,7 +141,7 @@ let editMember (model : EditMember) (types : (string * LocalizedString) seq) ctx
|
||||
/// View for the small group log on page
|
||||
let logOn (groups : SmallGroup list) grpId ctx viewInfo =
|
||||
let s = I18N.localizer.Force ()
|
||||
let model = { SmallGroupId = System.Guid.Empty; Password = ""; RememberMe = None }
|
||||
let model = { SmallGroupId = emptyGuid; Password = ""; RememberMe = None }
|
||||
let vi = AppViewInfo.withOnLoadScript "PT.smallGroup.logOn.onPageLoad" viewInfo
|
||||
form [ _action "/small-group/log-on/submit"; _method "post"; _class "pt-center-columns"; Target.body ] [
|
||||
csrfToken ctx
|
||||
@ -153,7 +154,7 @@ let logOn (groups : SmallGroup list) grpId ctx viewInfo =
|
||||
"", selectDefault s["Select Group"].Value
|
||||
yield!
|
||||
groups
|
||||
|> List.map (fun grp -> flatGuid grp.smallGroupId, $"{grp.church.name} | {grp.name}")
|
||||
|> List.map (fun grp -> shortGuid grp.Id.Value, $"{grp.Church.Name} | {grp.Name}")
|
||||
}
|
||||
|> selectList (nameof model.SmallGroupId) grpId [ _required ]
|
||||
]
|
||||
@ -187,10 +188,10 @@ let maintain (groups : SmallGroup list) ctx viewInfo =
|
||||
tableHeadings s [ "Actions"; "Name"; "Church"; "Time Zone"]
|
||||
groups
|
||||
|> List.map (fun g ->
|
||||
let grpId = flatGuid g.smallGroupId
|
||||
let grpId = shortGuid g.Id.Value
|
||||
let delAction = $"/small-group/{grpId}/delete"
|
||||
let delPrompt = s["Are you sure you want to delete this {0}? This action cannot be undone.",
|
||||
$"""{s["Small Group"].Value.ToLower ()} ({g.name})""" ].Value
|
||||
$"""{s["Small Group"].Value.ToLower ()} ({g.Name})""" ].Value
|
||||
tr [] [
|
||||
td [] [
|
||||
a [ _href $"/small-group/{grpId}/edit"; _title s["Edit This Group"].Value ] [ icon "edit" ]
|
||||
@ -200,9 +201,9 @@ let maintain (groups : SmallGroup list) ctx viewInfo =
|
||||
icon "delete_forever"
|
||||
]
|
||||
]
|
||||
td [] [ str g.name ]
|
||||
td [] [ str g.church.name ]
|
||||
td [] [ locStr (TimeZones.name g.preferences.timeZoneId s) ]
|
||||
td [] [ str g.Name ]
|
||||
td [] [ str g.Church.Name ]
|
||||
td [] [ locStr (TimeZones.name g.Preferences.TimeZoneId s) ]
|
||||
])
|
||||
|> tbody []
|
||||
]
|
||||
@ -233,11 +234,11 @@ let members (members : Member list) (emailTypes : Map<string, LocalizedString>)
|
||||
tableHeadings s [ "Actions"; "Name"; "E-mail Address"; "Format"]
|
||||
members
|
||||
|> List.map (fun mbr ->
|
||||
let mbrId = flatGuid mbr.memberId
|
||||
let mbrId = shortGuid mbr.Id.Value
|
||||
let delAction = $"/small-group/member/{mbrId}/delete"
|
||||
let delPrompt =
|
||||
s["Are you sure you want to delete this {0}? This action cannot be undone.", s["group member"]]
|
||||
.Value.Replace("?", $" ({mbr.memberName})?")
|
||||
.Value.Replace("?", $" ({mbr.Name})?")
|
||||
tr [] [
|
||||
td [] [
|
||||
a [ _href $"/small-group/member/{mbrId}/edit"; _title s["Edit This Group Member"].Value ] [
|
||||
@ -249,9 +250,9 @@ let members (members : Member list) (emailTypes : Map<string, LocalizedString>)
|
||||
icon "delete_forever"
|
||||
]
|
||||
]
|
||||
td [] [ str mbr.memberName ]
|
||||
td [] [ str mbr.email ]
|
||||
td [] [ locStr emailTypes[defaultArg mbr.format ""] ]
|
||||
td [] [ str mbr.Name ]
|
||||
td [] [ str mbr.Email ]
|
||||
td [] [ locStr emailTypes[defaultArg (mbr.Format |> Option.map EmailFormat.toCode) ""] ]
|
||||
])
|
||||
|> tbody []
|
||||
]
|
||||
@ -326,7 +327,6 @@ let overview model viewInfo =
|
||||
|
||||
|
||||
open System.IO
|
||||
open PrayerTracker
|
||||
|
||||
/// View for the small group preferences page
|
||||
let preferences (model : EditPreferences) (tzs : TimeZone list) ctx viewInfo =
|
||||
@ -494,7 +494,10 @@ let preferences (model : EditPreferences) (tzs : TimeZone list) ctx viewInfo =
|
||||
label [ _for (nameof model.TimeZone) ] [ locStr s["Time Zone"] ]
|
||||
seq {
|
||||
"", selectDefault s["Select"].Value
|
||||
yield! tzs |> List.map (fun tz -> tz.timeZoneId, (TimeZones.name tz.timeZoneId s).Value)
|
||||
yield!
|
||||
tzs
|
||||
|> List.map (fun tz ->
|
||||
TimeZoneId.toString tz.Id, (TimeZones.name tz.Id s).Value)
|
||||
}
|
||||
|> selectList (nameof model.TimeZone) model.TimeZone [ _required ]
|
||||
]
|
||||
|
@ -1,6 +1,7 @@
|
||||
module PrayerTracker.Views.User
|
||||
|
||||
open Giraffe.ViewEngine
|
||||
open PrayerTracker
|
||||
open PrayerTracker.ViewModels
|
||||
|
||||
/// View for the group assignment page
|
||||
@ -9,7 +10,7 @@ let assignGroups model groups curGroups ctx viewInfo =
|
||||
let pageTitle = sprintf "%s • %A" model.UserName s["Assign Groups"]
|
||||
form [ _action "/user/small-groups/save"; _method "post"; _class "pt-center-columns"; Target.content ] [
|
||||
csrfToken ctx
|
||||
inputField "hidden" (nameof model.UserId) (flatGuid model.UserId) []
|
||||
inputField "hidden" (nameof model.UserId) model.UserId []
|
||||
inputField "hidden" (nameof model.UserName) model.UserName []
|
||||
table [ _class "pt-table" ] [
|
||||
thead [] [
|
||||
@ -108,7 +109,7 @@ let edit (model : EditUser) ctx viewInfo =
|
||||
_onsubmit $"""return PT.compareValidation('{nameof model.Password}','{nameof model.PasswordConfirm}','%A{s["The passwords do not match"]}')"""
|
||||
Target.content ] [
|
||||
csrfToken ctx
|
||||
inputField "hidden" (nameof model.UserId) (flatGuid model.UserId) []
|
||||
inputField "hidden" (nameof model.UserId) model.UserId []
|
||||
div [ _fieldRow ] [
|
||||
div [ _inputField ] [
|
||||
label [ _for (nameof model.FirstName) ] [ locStr s["First Name"] ]
|
||||
@ -195,7 +196,7 @@ let maintain (users : User list) ctx viewInfo =
|
||||
tableHeadings s [ "Actions"; "Name"; "Admin?" ]
|
||||
users
|
||||
|> List.map (fun user ->
|
||||
let userId = flatGuid user.userId
|
||||
let userId = shortGuid user.Id.Value
|
||||
let delAction = $"/user/{userId}/delete"
|
||||
let delPrompt = s["Are you sure you want to delete this {0}? This action cannot be undone.",
|
||||
$"""{s["User"].Value.ToLower ()} ({user.fullName})"""].Value
|
||||
@ -213,7 +214,7 @@ let maintain (users : User list) ctx viewInfo =
|
||||
]
|
||||
td [] [ str user.fullName ]
|
||||
td [ _class "pt-center-text" ] [
|
||||
if user.isAdmin then strong [] [ locStr s["Yes"] ] else locStr s["No"]
|
||||
if user.IsAdmin then strong [] [ locStr s["Yes"] ] else locStr s["No"]
|
||||
]
|
||||
])
|
||||
|> tbody []
|
||||
|
@ -12,12 +12,23 @@ let sha1Hash (x : string) =
|
||||
|> Seq.map (fun chr -> chr.ToString "x2")
|
||||
|> String.concat ""
|
||||
|
||||
|
||||
/// Hash a string using 1,024 rounds of PBKDF2 and a salt
|
||||
let pbkdf2Hash (salt : Guid) (x : string) =
|
||||
use alg = new Rfc2898DeriveBytes (x, Encoding.UTF8.GetBytes (salt.ToString "N"), 1024)
|
||||
(alg.GetBytes >> Convert.ToBase64String) 64
|
||||
|
||||
open Giraffe
|
||||
|
||||
/// Parse a short-GUID-based ID from a string
|
||||
let idFromShort<'T> (f : Guid -> 'T) strValue =
|
||||
(ShortGuid.toGuid >> f) strValue
|
||||
|
||||
/// Format a GUID as a short GUID
|
||||
let shortGuid = ShortGuid.fromGuid
|
||||
|
||||
/// An empty short GUID string (used for "add" actions)
|
||||
let emptyGuid = shortGuid Guid.Empty
|
||||
|
||||
|
||||
/// String helper functions
|
||||
module String =
|
||||
|
@ -10,11 +10,11 @@ open PrayerTracker.Entities
|
||||
module ReferenceList =
|
||||
|
||||
/// A localized list of the AsOfDateDisplay DU cases
|
||||
let asOfDateList (s : IStringLocalizer) =
|
||||
[ NoDisplay.code, s["Do not display the “as of” date"]
|
||||
ShortDate.code, s["Display a short “as of” date"]
|
||||
LongDate.code, s["Display a full “as of” date"]
|
||||
]
|
||||
let asOfDateList (s : IStringLocalizer) = [
|
||||
AsOfDateDisplay.toCode NoDisplay, s["Do not display the “as of” date"]
|
||||
AsOfDateDisplay.toCode ShortDate, s["Display a short “as of” date"]
|
||||
AsOfDateDisplay.toCode LongDate, s["Display a full “as of” date"]
|
||||
]
|
||||
|
||||
/// A list of e-mail type options
|
||||
let emailTypeList def (s : IStringLocalizer) =
|
||||
@ -22,26 +22,26 @@ module ReferenceList =
|
||||
let defaultType =
|
||||
s[match def with HtmlFormat -> "HTML Format" | PlainTextFormat -> "Plain-Text Format"].Value
|
||||
seq {
|
||||
"", LocalizedString ("", $"""{s["Group Default"].Value} ({defaultType})""")
|
||||
HtmlFormat.code, s["HTML Format"]
|
||||
PlainTextFormat.code, s["Plain-Text Format"]
|
||||
"", LocalizedString ("", $"""{s["Group Default"].Value} ({defaultType})""")
|
||||
EmailFormat.toCode HtmlFormat, s["HTML Format"]
|
||||
EmailFormat.toCode PlainTextFormat, s["Plain-Text Format"]
|
||||
}
|
||||
|
||||
/// A list of expiration options
|
||||
let expirationList (s : IStringLocalizer) includeExpireNow =
|
||||
[ Automatic.code, s["Expire Normally"]
|
||||
Manual.code, s["Request Never Expires"]
|
||||
if includeExpireNow then Forced.code, s["Expire Immediately"]
|
||||
]
|
||||
let expirationList (s : IStringLocalizer) includeExpireNow = [
|
||||
Expiration.toCode Automatic, s["Expire Normally"]
|
||||
Expiration.toCode Manual, s["Request Never Expires"]
|
||||
if includeExpireNow then Expiration.toCode Forced, s["Expire Immediately"]
|
||||
]
|
||||
|
||||
/// A list of request types
|
||||
let requestTypeList (s : IStringLocalizer) =
|
||||
[ CurrentRequest, s["Current Requests"]
|
||||
LongTermRequest, s["Long-Term Requests"]
|
||||
PraiseReport, s["Praise Reports"]
|
||||
Expecting, s["Expecting"]
|
||||
Announcement, s["Announcements"]
|
||||
]
|
||||
let requestTypeList (s : IStringLocalizer) = [
|
||||
CurrentRequest, s["Current Requests"]
|
||||
LongTermRequest, s["Long-Term Requests"]
|
||||
PraiseReport, s["Praise Reports"]
|
||||
Expecting, s["Expecting"]
|
||||
Announcement, s["Announcements"]
|
||||
]
|
||||
|
||||
|
||||
/// A user message level
|
||||
@ -209,7 +209,7 @@ with
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
type AssignGroups =
|
||||
{ /// The Id of the user being assigned
|
||||
UserId : UserId
|
||||
UserId : string
|
||||
|
||||
/// The full name of the user being assigned
|
||||
UserName : string
|
||||
@ -222,10 +222,10 @@ type AssignGroups =
|
||||
module AssignGroups =
|
||||
|
||||
/// Create an instance of this form from an existing user
|
||||
let fromUser (u : User) =
|
||||
{ UserId = u.userId
|
||||
UserName = u.fullName
|
||||
SmallGroups = ""
|
||||
let fromUser (user : User) =
|
||||
{ UserId = shortGuid user.Id.Value
|
||||
UserName = user.fullName
|
||||
SmallGroups = ""
|
||||
}
|
||||
|
||||
|
||||
@ -246,8 +246,8 @@ type ChangePassword =
|
||||
/// Form for adding or editing a church
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
type EditChurch =
|
||||
{ /// The Id of the church
|
||||
ChurchId : ChurchId
|
||||
{ /// The ID of the church
|
||||
ChurchId : string
|
||||
|
||||
/// The name of the church
|
||||
Name : string
|
||||
@ -267,40 +267,39 @@ type EditChurch =
|
||||
with
|
||||
|
||||
/// Is this a new church?
|
||||
member this.IsNew
|
||||
with get () = Guid.Empty = this.ChurchId
|
||||
member this.IsNew = emptyGuid = this.ChurchId
|
||||
|
||||
/// Populate a church from this form
|
||||
member this.PopulateChurch (church : Church) =
|
||||
{ church with
|
||||
name = this.Name
|
||||
city = this.City
|
||||
st = this.State
|
||||
hasInterface = match this.HasInterface with Some x -> x | None -> false
|
||||
interfaceAddress = match this.HasInterface with Some x when x -> this.InterfaceAddress | _ -> None
|
||||
Name = this.Name
|
||||
City = this.City
|
||||
State = this.State
|
||||
HasInterface = match this.HasInterface with Some x -> x | None -> false
|
||||
InterfaceAddress = match this.HasInterface with Some x when x -> this.InterfaceAddress | _ -> None
|
||||
}
|
||||
|
||||
/// Support for the EditChurch type
|
||||
module EditChurch =
|
||||
|
||||
/// Create an instance from an existing church
|
||||
let fromChurch (ch : Church) =
|
||||
{ ChurchId = ch.churchId
|
||||
Name = ch.name
|
||||
City = ch.city
|
||||
State = ch.st
|
||||
HasInterface = match ch.hasInterface with true -> Some true | false -> None
|
||||
InterfaceAddress = ch.interfaceAddress
|
||||
let fromChurch (church : Church) =
|
||||
{ ChurchId = shortGuid church.Id.Value
|
||||
Name = church.Name
|
||||
City = church.City
|
||||
State = church.State
|
||||
HasInterface = match church.HasInterface with true -> Some true | false -> None
|
||||
InterfaceAddress = church.InterfaceAddress
|
||||
}
|
||||
|
||||
/// An instance to use for adding churches
|
||||
let empty =
|
||||
{ ChurchId = Guid.Empty
|
||||
Name = ""
|
||||
City = ""
|
||||
State = ""
|
||||
HasInterface = None
|
||||
InterfaceAddress = None
|
||||
{ ChurchId = emptyGuid
|
||||
Name = ""
|
||||
City = ""
|
||||
State = ""
|
||||
HasInterface = None
|
||||
InterfaceAddress = None
|
||||
}
|
||||
|
||||
|
||||
@ -308,7 +307,7 @@ module EditChurch =
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
type EditMember =
|
||||
{ /// The Id for this small group member (not user-entered)
|
||||
MemberId : MemberId
|
||||
MemberId : string
|
||||
|
||||
/// The name of the member
|
||||
Name : string
|
||||
@ -322,26 +321,25 @@ type EditMember =
|
||||
with
|
||||
|
||||
/// Is this a new member?
|
||||
member this.IsNew
|
||||
with get () = Guid.Empty = this.MemberId
|
||||
member this.IsNew = emptyGuid = this.MemberId
|
||||
|
||||
/// Support for the EditMember type
|
||||
module EditMember =
|
||||
|
||||
/// Create an instance from an existing member
|
||||
let fromMember (m : Member) =
|
||||
{ MemberId = m.memberId
|
||||
Name = m.memberName
|
||||
Email = m.email
|
||||
Format = match m.format with Some f -> f | None -> ""
|
||||
let fromMember (mbr : Member) =
|
||||
{ MemberId = shortGuid mbr.Id.Value
|
||||
Name = mbr.Name
|
||||
Email = mbr.Email
|
||||
Format = match mbr.Format with Some fmt -> EmailFormat.toCode fmt | None -> ""
|
||||
}
|
||||
|
||||
/// An empty instance
|
||||
let empty =
|
||||
{ MemberId = Guid.Empty
|
||||
Name = ""
|
||||
Email = ""
|
||||
Format = ""
|
||||
{ MemberId = emptyGuid
|
||||
Name = ""
|
||||
Email = ""
|
||||
Format = ""
|
||||
}
|
||||
|
||||
|
||||
@ -416,23 +414,23 @@ with
|
||||
| RequestVisibility.``private``
|
||||
| _ -> false, ""
|
||||
{ prefs with
|
||||
daysToExpire = this.ExpireDays
|
||||
daysToKeepNew = this.DaysToKeepNew
|
||||
longTermUpdateWeeks = this.LongTermUpdateWeeks
|
||||
requestSort = RequestSort.fromCode this.RequestSort
|
||||
emailFromName = this.EmailFromName
|
||||
emailFromAddress = this.EmailFromAddress
|
||||
defaultEmailType = EmailFormat.fromCode this.DefaultEmailType
|
||||
lineColor = this.LineColor
|
||||
headingColor = this.HeadingColor
|
||||
listFonts = this.Fonts
|
||||
headingFontSize = this.HeadingFontSize
|
||||
textFontSize = this.ListFontSize
|
||||
timeZoneId = this.TimeZone
|
||||
isPublic = isPublic
|
||||
groupPassword = grpPw
|
||||
pageSize = this.PageSize
|
||||
asOfDateDisplay = AsOfDateDisplay.fromCode this.AsOfDate
|
||||
DaysToExpire = this.ExpireDays
|
||||
DaysToKeepNew = this.DaysToKeepNew
|
||||
LongTermUpdateWeeks = this.LongTermUpdateWeeks
|
||||
RequestSort = RequestSort.fromCode this.RequestSort
|
||||
EmailFromName = this.EmailFromName
|
||||
EmailFromAddress = this.EmailFromAddress
|
||||
DefaultEmailType = EmailFormat.fromCode this.DefaultEmailType
|
||||
LineColor = this.LineColor
|
||||
HeadingColor = this.HeadingColor
|
||||
Fonts = this.Fonts
|
||||
HeadingFontSize = this.HeadingFontSize
|
||||
TextFontSize = this.ListFontSize
|
||||
TimeZoneId = TimeZoneId this.TimeZone
|
||||
IsPublic = isPublic
|
||||
GroupPassword = grpPw
|
||||
PageSize = this.PageSize
|
||||
AsOfDateDisplay = AsOfDateDisplay.fromCode this.AsOfDate
|
||||
}
|
||||
|
||||
/// Support for the EditPreferences type
|
||||
@ -440,37 +438,37 @@ module EditPreferences =
|
||||
/// Populate an edit form from existing preferences
|
||||
let fromPreferences (prefs : ListPreferences) =
|
||||
let setType (x : string) = match x.StartsWith "#" with true -> "RGB" | false -> "Name"
|
||||
{ ExpireDays = prefs.daysToExpire
|
||||
DaysToKeepNew = prefs.daysToKeepNew
|
||||
LongTermUpdateWeeks = prefs.longTermUpdateWeeks
|
||||
RequestSort = prefs.requestSort.code
|
||||
EmailFromName = prefs.emailFromName
|
||||
EmailFromAddress = prefs.emailFromAddress
|
||||
DefaultEmailType = prefs.defaultEmailType.code
|
||||
LineColorType = setType prefs.lineColor
|
||||
LineColor = prefs.lineColor
|
||||
HeadingColorType = setType prefs.headingColor
|
||||
HeadingColor = prefs.headingColor
|
||||
Fonts = prefs.listFonts
|
||||
HeadingFontSize = prefs.headingFontSize
|
||||
ListFontSize = prefs.textFontSize
|
||||
TimeZone = prefs.timeZoneId
|
||||
GroupPassword = Some prefs.groupPassword
|
||||
PageSize = prefs.pageSize
|
||||
AsOfDate = prefs.asOfDateDisplay.code
|
||||
Visibility =
|
||||
match true with
|
||||
| _ when prefs.isPublic -> RequestVisibility.``public``
|
||||
| _ when prefs.groupPassword = "" -> RequestVisibility.``private``
|
||||
| _ -> RequestVisibility.passwordProtected
|
||||
{ ExpireDays = prefs.DaysToExpire
|
||||
DaysToKeepNew = prefs.DaysToKeepNew
|
||||
LongTermUpdateWeeks = prefs.LongTermUpdateWeeks
|
||||
RequestSort = RequestSort.toCode prefs.RequestSort
|
||||
EmailFromName = prefs.EmailFromName
|
||||
EmailFromAddress = prefs.EmailFromAddress
|
||||
DefaultEmailType = EmailFormat.toCode prefs.DefaultEmailType
|
||||
LineColorType = setType prefs.LineColor
|
||||
LineColor = prefs.LineColor
|
||||
HeadingColorType = setType prefs.HeadingColor
|
||||
HeadingColor = prefs.HeadingColor
|
||||
Fonts = prefs.Fonts
|
||||
HeadingFontSize = prefs.HeadingFontSize
|
||||
ListFontSize = prefs.TextFontSize
|
||||
TimeZone = TimeZoneId.toString prefs.TimeZoneId
|
||||
GroupPassword = Some prefs.GroupPassword
|
||||
PageSize = prefs.PageSize
|
||||
AsOfDate = AsOfDateDisplay.toCode prefs.AsOfDateDisplay
|
||||
Visibility =
|
||||
match true with
|
||||
| _ when prefs.IsPublic -> RequestVisibility.``public``
|
||||
| _ when prefs.GroupPassword = "" -> RequestVisibility.``private``
|
||||
| _ -> RequestVisibility.passwordProtected
|
||||
}
|
||||
|
||||
|
||||
/// Form for adding or editing prayer requests
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
type EditRequest =
|
||||
{ /// The Id of the request
|
||||
RequestId : PrayerRequestId
|
||||
{ /// The ID of the request
|
||||
RequestId : string
|
||||
|
||||
/// The type of the request
|
||||
RequestType : string
|
||||
@ -493,82 +491,80 @@ type EditRequest =
|
||||
with
|
||||
|
||||
/// Is this a new request?
|
||||
member this.IsNew
|
||||
with get () = Guid.Empty = this.RequestId
|
||||
member this.IsNew = emptyGuid = this.RequestId
|
||||
|
||||
/// Support for the EditRequest type
|
||||
module EditRequest =
|
||||
|
||||
/// An empty instance to use for new requests
|
||||
let empty =
|
||||
{ RequestId = Guid.Empty
|
||||
RequestType = CurrentRequest.code
|
||||
EnteredDate = None
|
||||
SkipDateUpdate = None
|
||||
Requestor = None
|
||||
Expiration = Automatic.code
|
||||
Text = ""
|
||||
{ RequestId = emptyGuid
|
||||
RequestType = PrayerRequestType.toCode CurrentRequest
|
||||
EnteredDate = None
|
||||
SkipDateUpdate = None
|
||||
Requestor = None
|
||||
Expiration = Expiration.toCode Automatic
|
||||
Text = ""
|
||||
}
|
||||
|
||||
/// Create an instance from an existing request
|
||||
let fromRequest req =
|
||||
let fromRequest (req : PrayerRequest) =
|
||||
{ empty with
|
||||
RequestId = req.prayerRequestId
|
||||
RequestType = req.requestType.code
|
||||
Requestor = req.requestor
|
||||
Expiration = req.expiration.code
|
||||
Text = req.text
|
||||
RequestId = shortGuid req.Id.Value
|
||||
RequestType = PrayerRequestType.toCode req.RequestType
|
||||
Requestor = req.Requestor
|
||||
Expiration = Expiration.toCode req.Expiration
|
||||
Text = req.Text
|
||||
}
|
||||
|
||||
|
||||
/// Form for the admin-level editing of small groups
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
type EditSmallGroup =
|
||||
{ /// The Id of the small group
|
||||
SmallGroupId : SmallGroupId
|
||||
{ /// The ID of the small group
|
||||
SmallGroupId : string
|
||||
|
||||
/// The name of the small group
|
||||
Name : string
|
||||
|
||||
/// The Id of the church to which this small group belongs
|
||||
ChurchId : ChurchId
|
||||
/// The ID of the church to which this small group belongs
|
||||
ChurchId : string
|
||||
}
|
||||
with
|
||||
|
||||
/// Is this a new small group?
|
||||
member this.IsNew
|
||||
with get () = Guid.Empty = this.SmallGroupId
|
||||
member this.IsNew = emptyGuid = this.SmallGroupId
|
||||
|
||||
/// Populate a small group from this form
|
||||
member this.populateGroup (grp : SmallGroup) =
|
||||
{ grp with
|
||||
name = this.Name
|
||||
churchId = this.ChurchId
|
||||
Name = this.Name
|
||||
ChurchId = idFromShort ChurchId this.ChurchId
|
||||
}
|
||||
|
||||
/// Support for the EditSmallGroup type
|
||||
module EditSmallGroup =
|
||||
|
||||
/// Create an instance from an existing small group
|
||||
let fromGroup (g : SmallGroup) =
|
||||
{ SmallGroupId = g.smallGroupId
|
||||
Name = g.name
|
||||
ChurchId = g.churchId
|
||||
let fromGroup (grp : SmallGroup) =
|
||||
{ SmallGroupId = shortGuid grp.Id.Value
|
||||
Name = grp.Name
|
||||
ChurchId = shortGuid grp.ChurchId.Value
|
||||
}
|
||||
|
||||
/// An empty instance (used when adding a new group)
|
||||
let empty =
|
||||
{ SmallGroupId = Guid.Empty
|
||||
Name = ""
|
||||
ChurchId = Guid.Empty
|
||||
{ SmallGroupId = emptyGuid
|
||||
Name = ""
|
||||
ChurchId = emptyGuid
|
||||
}
|
||||
|
||||
|
||||
/// Form for the user edit page
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
type EditUser =
|
||||
{ /// The Id of the user
|
||||
UserId : UserId
|
||||
{ /// The ID of the user
|
||||
UserId : string
|
||||
|
||||
/// The first name of the user
|
||||
FirstName : string
|
||||
@ -591,43 +587,42 @@ type EditUser =
|
||||
with
|
||||
|
||||
/// Is this a new user?
|
||||
member this.IsNew
|
||||
with get () = Guid.Empty = this.UserId
|
||||
member this.IsNew = emptyGuid = this.UserId
|
||||
|
||||
/// Populate a user from the form
|
||||
member this.PopulateUser (user : User) hasher =
|
||||
{ user with
|
||||
firstName = this.FirstName
|
||||
lastName = this.LastName
|
||||
emailAddress = this.Email
|
||||
isAdmin = defaultArg this.IsAdmin false
|
||||
}
|
||||
FirstName = this.FirstName
|
||||
LastName = this.LastName
|
||||
Email = this.Email
|
||||
IsAdmin = defaultArg this.IsAdmin false
|
||||
}
|
||||
|> function
|
||||
| u when isNull this.Password || this.Password = "" -> u
|
||||
| u -> { u with passwordHash = hasher this.Password }
|
||||
| u -> { u with PasswordHash = hasher this.Password }
|
||||
|
||||
/// Support for the EditUser type
|
||||
module EditUser =
|
||||
|
||||
/// An empty instance
|
||||
let empty =
|
||||
{ UserId = Guid.Empty
|
||||
FirstName = ""
|
||||
LastName = ""
|
||||
Email = ""
|
||||
Password = ""
|
||||
PasswordConfirm = ""
|
||||
IsAdmin = None
|
||||
{ UserId = emptyGuid
|
||||
FirstName = ""
|
||||
LastName = ""
|
||||
Email = ""
|
||||
Password = ""
|
||||
PasswordConfirm = ""
|
||||
IsAdmin = None
|
||||
}
|
||||
|
||||
/// Create an instance from an existing user
|
||||
let fromUser (user : User) =
|
||||
{ empty with
|
||||
UserId = user.userId
|
||||
FirstName = user.firstName
|
||||
LastName = user.lastName
|
||||
Email = user.emailAddress
|
||||
IsAdmin = if user.isAdmin then Some true else None
|
||||
UserId = shortGuid user.Id.Value
|
||||
FirstName = user.FirstName
|
||||
LastName = user.LastName
|
||||
Email = user.Email
|
||||
IsAdmin = if user.IsAdmin then Some true else None
|
||||
}
|
||||
|
||||
|
||||
@ -635,7 +630,7 @@ module EditUser =
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
type GroupLogOn =
|
||||
{ /// The ID of the small group to which the user is logging on
|
||||
SmallGroupId : SmallGroupId
|
||||
SmallGroupId : string
|
||||
|
||||
/// The password entered
|
||||
Password : string
|
||||
@ -649,9 +644,9 @@ module GroupLogOn =
|
||||
|
||||
/// An empty instance
|
||||
let empty =
|
||||
{ SmallGroupId = Guid.Empty
|
||||
Password = ""
|
||||
RememberMe = None
|
||||
{ SmallGroupId = emptyGuid
|
||||
Password = ""
|
||||
RememberMe = None
|
||||
}
|
||||
|
||||
|
||||
@ -679,11 +674,11 @@ module MaintainRequests =
|
||||
|
||||
/// An empty instance
|
||||
let empty =
|
||||
{ Requests = []
|
||||
SmallGroup = SmallGroup.empty
|
||||
OnlyActive = None
|
||||
SearchTerm = None
|
||||
PageNbr = None
|
||||
{ Requests = []
|
||||
SmallGroup = SmallGroup.empty
|
||||
OnlyActive = None
|
||||
SearchTerm = None
|
||||
PageNbr = None
|
||||
}
|
||||
|
||||
|
||||
@ -714,7 +709,7 @@ type UserLogOn =
|
||||
Password : string
|
||||
|
||||
/// The ID of the small group to which the user is logging on
|
||||
SmallGroupId : SmallGroupId
|
||||
SmallGroupId : string
|
||||
|
||||
/// Whether to remember the login
|
||||
RememberMe : bool option
|
||||
@ -728,11 +723,11 @@ module UserLogOn =
|
||||
|
||||
/// An empty instance
|
||||
let empty =
|
||||
{ Email = ""
|
||||
Password = ""
|
||||
SmallGroupId = Guid.Empty
|
||||
RememberMe = None
|
||||
RedirectUrl = None
|
||||
{ Email = ""
|
||||
Password = ""
|
||||
SmallGroupId = emptyGuid
|
||||
RememberMe = None
|
||||
RedirectUrl = None
|
||||
}
|
||||
|
||||
|
||||
@ -765,13 +760,13 @@ with
|
||||
ReferenceList.requestTypeList s
|
||||
|> List.map (fun (typ, name) ->
|
||||
let sort =
|
||||
match this.SmallGroup.preferences.requestSort with
|
||||
| SortByDate -> Seq.sortByDescending (fun req -> req.updatedDate)
|
||||
| SortByRequestor -> Seq.sortBy (fun req -> req.requestor)
|
||||
match this.SmallGroup.Preferences.RequestSort with
|
||||
| SortByDate -> Seq.sortByDescending (fun req -> req.UpdatedDate)
|
||||
| SortByRequestor -> Seq.sortBy (fun req -> req.Requestor)
|
||||
let reqs =
|
||||
this.Requests
|
||||
|> Seq.ofList
|
||||
|> Seq.filter (fun req -> req.requestType = typ)
|
||||
|> Seq.filter (fun req -> req.RequestType = typ)
|
||||
|> sort
|
||||
|> List.ofSeq
|
||||
typ, name, reqs)
|
||||
@ -779,20 +774,20 @@ with
|
||||
|
||||
/// Is this request new?
|
||||
member this.IsNew (req : PrayerRequest) =
|
||||
(this.Date - req.updatedDate).Days <= this.SmallGroup.preferences.daysToKeepNew
|
||||
(this.Date - req.UpdatedDate).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 prefs = this.SmallGroup.Preferences
|
||||
let asOfSize = Math.Round (float prefs.TextFontSize * 0.8, 2)
|
||||
[ if this.ShowHeader then
|
||||
div [ _style $"text-align:center;font-family:{prefs.listFonts}" ] [
|
||||
span [ _style $"font-size:%i{prefs.headingFontSize}pt;" ] [
|
||||
div [ _style $"text-align:center;font-family:{prefs.Fonts}" ] [
|
||||
span [ _style $"font-size:%i{prefs.HeadingFontSize}pt;" ] [
|
||||
strong [] [ str s["Prayer Requests"].Value ]
|
||||
]
|
||||
br []
|
||||
span [ _style $"font-size:%i{prefs.textFontSize}pt;" ] [
|
||||
strong [] [ str this.SmallGroup.name ]
|
||||
span [ _style $"font-size:%i{prefs.TextFontSize}pt;" ] [
|
||||
strong [] [ str this.SmallGroup.Name ]
|
||||
br []
|
||||
str (this.Date.ToString s["MMMM d, yyyy"].Value)
|
||||
]
|
||||
@ -800,9 +795,9 @@ with
|
||||
br []
|
||||
for _, name, reqs in this.RequestsByType s do
|
||||
div [ _style "padding-left:10px;padding-bottom:.5em;" ] [
|
||||
table [ _style $"font-family:{prefs.listFonts};page-break-inside:avoid;" ] [
|
||||
table [ _style $"font-family:{prefs.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{prefs.HeadingFontSize}pt;color:{prefs.HeadingColor};padding:3px 0;border-top:solid 3px {prefs.LineColor};border-bottom:solid 3px {prefs.LineColor};font-weight:bold;" ] [
|
||||
rawText " "; str name.Value; rawText " "
|
||||
]
|
||||
]
|
||||
@ -811,22 +806,22 @@ 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.listFonts};font-size:%i{prefs.textFontSize}pt;padding-bottom:.25em;" ] [
|
||||
match req.requestor with
|
||||
li [ _style $"list-style-type:{bullet};font-family:{prefs.Fonts};font-size:%i{prefs.TextFontSize}pt;padding-bottom:.25em;" ] [
|
||||
match req.Requestor with
|
||||
| Some r when r <> "" ->
|
||||
strong [] [ str r ]
|
||||
rawText " — "
|
||||
| Some _ -> ()
|
||||
| None -> ()
|
||||
rawText req.text
|
||||
match prefs.asOfDateDisplay with
|
||||
rawText req.Text
|
||||
match prefs.AsOfDateDisplay with
|
||||
| NoDisplay -> ()
|
||||
| ShortDate
|
||||
| LongDate ->
|
||||
let dt =
|
||||
match prefs.asOfDateDisplay with
|
||||
| ShortDate -> req.updatedDate.ToShortDateString ()
|
||||
| LongDate -> req.updatedDate.ToLongDateString ()
|
||||
match prefs.AsOfDateDisplay with
|
||||
| ShortDate -> req.UpdatedDate.ToShortDateString ()
|
||||
| LongDate -> req.UpdatedDate.ToLongDateString ()
|
||||
| _ -> ""
|
||||
i [ _style $"font-size:%.2f{asOfSize}pt" ] [
|
||||
rawText " ("; str s["as of"].Value; str " "; str dt; rawText ")"
|
||||
@ -840,7 +835,7 @@ with
|
||||
/// Generate this list as plain text
|
||||
member this.AsText (s : IStringLocalizer) =
|
||||
seq {
|
||||
this.SmallGroup.name
|
||||
this.SmallGroup.Name
|
||||
s["Prayer Requests"].Value
|
||||
this.Date.ToString s["MMMM d, yyyy"].Value
|
||||
" "
|
||||
@ -851,17 +846,17 @@ with
|
||||
dashes
|
||||
for req in reqs do
|
||||
let bullet = if this.IsNew req then "+" else "-"
|
||||
let requestor = match req.requestor with Some r -> $"{r} - " | None -> ""
|
||||
match this.SmallGroup.preferences.asOfDateDisplay with
|
||||
let requestor = match req.Requestor with Some r -> $"{r} - " | None -> ""
|
||||
match this.SmallGroup.Preferences.AsOfDateDisplay with
|
||||
| NoDisplay -> ""
|
||||
| _ ->
|
||||
let dt =
|
||||
match this.SmallGroup.preferences.asOfDateDisplay with
|
||||
| ShortDate -> req.updatedDate.ToShortDateString ()
|
||||
| LongDate -> req.updatedDate.ToLongDateString ()
|
||||
match this.SmallGroup.Preferences.AsOfDateDisplay with
|
||||
| ShortDate -> req.UpdatedDate.ToShortDateString ()
|
||||
| LongDate -> req.UpdatedDate.ToLongDateString ()
|
||||
| _ -> ""
|
||||
$""" ({s["as of"].Value} {dt})"""
|
||||
|> sprintf " %s %s%s%s" bullet requestor (htmlToPlainText req.text)
|
||||
|> sprintf " %s %s%s%s" bullet requestor (htmlToPlainText req.Text)
|
||||
" "
|
||||
}
|
||||
|> String.concat "\n"
|
||||
|
@ -1,24 +1,21 @@
|
||||
module PrayerTracker.Handlers.Church
|
||||
|
||||
open System
|
||||
open System.Threading.Tasks
|
||||
open Giraffe
|
||||
open PrayerTracker
|
||||
open PrayerTracker.Entities
|
||||
open PrayerTracker.ViewModels
|
||||
open PrayerTracker.Views.CommonFunctions
|
||||
|
||||
/// Find statistics for the given church
|
||||
let private findStats (db : AppDbContext) churchId = task {
|
||||
let! grps = db.CountGroupsByChurch churchId
|
||||
let! reqs = db.CountRequestsByChurch churchId
|
||||
let! usrs = db.CountUsersByChurch churchId
|
||||
return flatGuid churchId, { smallGroups = grps; prayerRequests = reqs; users = usrs }
|
||||
return shortGuid churchId.Value, { SmallGroups = grps; PrayerRequests = reqs; Users = usrs }
|
||||
}
|
||||
|
||||
|
||||
/// POST /church/[church-id]/delete
|
||||
let delete churchId : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next ctx -> task {
|
||||
let delete chId : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next ctx -> task {
|
||||
let churchId = ChurchId chId
|
||||
match! ctx.db.TryChurchById churchId with
|
||||
| Some church ->
|
||||
let! _, stats = findStats ctx.db churchId
|
||||
@ -27,11 +24,12 @@ let delete churchId : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=>
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
addInfo ctx
|
||||
s["The church {0} and its {1} small groups (with {2} prayer request(s)) were deleted successfully; revoked access from {3} user(s)",
|
||||
church.name, stats.smallGroups, stats.prayerRequests, stats.users]
|
||||
church.Name, stats.SmallGroups, stats.PrayerRequests, stats.Users]
|
||||
return! redirectTo false "/churches" next ctx
|
||||
| None -> return! fourOhFour next ctx
|
||||
}
|
||||
|
||||
open System
|
||||
|
||||
/// GET /church/[church-id]/edit
|
||||
let edit churchId : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> task {
|
||||
@ -42,7 +40,7 @@ let edit churchId : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> ta
|
||||
|> Views.Church.edit EditChurch.empty ctx
|
||||
|> renderHtml next ctx
|
||||
else
|
||||
match! ctx.db.TryChurchById churchId with
|
||||
match! ctx.db.TryChurchById (ChurchId churchId) with
|
||||
| Some church ->
|
||||
return!
|
||||
viewInfo ctx startTicks
|
||||
@ -51,35 +49,35 @@ let edit churchId : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> ta
|
||||
| None -> return! fourOhFour next ctx
|
||||
}
|
||||
|
||||
|
||||
/// GET /churches
|
||||
let maintain : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> task {
|
||||
let startTicks = DateTime.Now.Ticks
|
||||
let await = Async.AwaitTask >> Async.RunSynchronously
|
||||
let! churches = ctx.db.AllChurches ()
|
||||
let stats = churches |> List.map (fun c -> await (findStats ctx.db c.churchId))
|
||||
let stats = churches |> List.map (fun c -> await (findStats ctx.db c.Id))
|
||||
return!
|
||||
viewInfo ctx startTicks
|
||||
|> Views.Church.maintain churches (stats |> Map.ofList) ctx
|
||||
|> renderHtml next ctx
|
||||
}
|
||||
|
||||
open System.Threading.Tasks
|
||||
|
||||
/// POST /church/save
|
||||
let save : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next ctx -> task {
|
||||
match! ctx.TryBindFormAsync<EditChurch> () with
|
||||
| Ok m ->
|
||||
| Ok model ->
|
||||
let! church =
|
||||
if m.IsNew then Task.FromResult (Some { Church.empty with churchId = Guid.NewGuid () })
|
||||
else ctx.db.TryChurchById m.ChurchId
|
||||
if model.IsNew then Task.FromResult (Some { Church.empty with Id = (Guid.NewGuid >> ChurchId) () })
|
||||
else ctx.db.TryChurchById (idFromShort ChurchId model.ChurchId)
|
||||
match church with
|
||||
| Some ch ->
|
||||
m.PopulateChurch ch
|
||||
|> (if m.IsNew then ctx.db.AddEntry else ctx.db.UpdateEntry)
|
||||
model.PopulateChurch ch
|
||||
|> (if model.IsNew then ctx.db.AddEntry else ctx.db.UpdateEntry)
|
||||
let! _ = ctx.db.SaveChangesAsync ()
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
let act = s[if m.IsNew then "Added" else "Updated"].Value.ToLower ()
|
||||
addInfo ctx s["Successfully {0} church “{1}”", act, m.Name]
|
||||
let act = s[if model.IsNew then "Added" else "Updated"].Value.ToLower ()
|
||||
addInfo ctx s["Successfully {0} church “{1}”", act, model.Name]
|
||||
return! redirectTo false "/churches" next ctx
|
||||
| None -> return! fourOhFour next ctx
|
||||
| Result.Error e -> return! bindError e next ctx
|
||||
|
@ -72,8 +72,8 @@ let viewInfo (ctx : HttpContext) startTicks =
|
||||
// The idle timeout is 2 hours; if the app pool is recycled or the actual session goes away, we will log the
|
||||
// user back in transparently using this cookie. Every request resets the timer.
|
||||
let timeout =
|
||||
{ Id = u.userId
|
||||
GroupId = (currentGroup ctx).smallGroupId
|
||||
{ Id = u.Id.Value
|
||||
GroupId = (currentGroup ctx).Id.Value
|
||||
Until = DateTime.UtcNow.AddHours(2.).Ticks
|
||||
Password = ""
|
||||
}
|
||||
@ -163,6 +163,7 @@ type AccessLevel =
|
||||
|
||||
|
||||
open Microsoft.AspNetCore.Http.Extensions
|
||||
open PrayerTracker.Entities
|
||||
|
||||
/// Require the given access role (also refreshes "Remember Me" user and group logons)
|
||||
let requireAccess level : HttpHandler =
|
||||
@ -177,11 +178,11 @@ let requireAccess level : HttpHandler =
|
||||
try
|
||||
match TimeoutCookie.fromPayload ctx.Request.Cookies[Key.Cookie.timeout] with
|
||||
| Some c when c.Password = saltedTimeoutHash c ->
|
||||
let! user = ctx.db.TryUserById c.Id
|
||||
let! user = ctx.db.TryUserById (UserId c.Id)
|
||||
match user with
|
||||
| Some _ ->
|
||||
ctx.Session.user <- user
|
||||
let! grp = ctx.db.TryGroupById c.GroupId
|
||||
let! grp = ctx.db.TryGroupById (SmallGroupId c.GroupId)
|
||||
ctx.Session.smallGroup <- grp
|
||||
| _ -> ()
|
||||
| _ -> ()
|
||||
@ -193,11 +194,11 @@ let requireAccess level : HttpHandler =
|
||||
let logOnUserFromCookie (ctx : HttpContext) = task {
|
||||
match UserCookie.fromPayload ctx.Request.Cookies[Key.Cookie.user] with
|
||||
| Some c ->
|
||||
let! user = ctx.db.TryUserLogOnByCookie c.Id c.GroupId c.PasswordHash
|
||||
let! user = ctx.db.TryUserLogOnByCookie (UserId c.Id) (SmallGroupId c.GroupId) c.PasswordHash
|
||||
match user with
|
||||
| Some _ ->
|
||||
ctx.Session.user <- user
|
||||
let! grp = ctx.db.TryGroupById c.GroupId
|
||||
let! grp = ctx.db.TryGroupById (SmallGroupId c.GroupId)
|
||||
ctx.Session.smallGroup <- grp
|
||||
// Rewrite the cookie to extend the expiration
|
||||
ctx.Response.Cookies.Append (Key.Cookie.user, c.toPayload (), autoRefresh)
|
||||
@ -213,7 +214,7 @@ let requireAccess level : HttpHandler =
|
||||
let logOnGroupFromCookie (ctx : HttpContext) = task {
|
||||
match GroupCookie.fromPayload ctx.Request.Cookies[Key.Cookie.group] with
|
||||
| Some c ->
|
||||
let! grp = ctx.db.TryGroupLogOnByCookie c.GroupId c.PasswordHash sha1Hash
|
||||
let! grp = ctx.db.TryGroupLogOnByCookie (SmallGroupId c.GroupId) c.PasswordHash sha1Hash
|
||||
match grp with
|
||||
| Some _ ->
|
||||
ctx.Session.smallGroup <- grp
|
||||
@ -236,7 +237,7 @@ let requireAccess level : HttpHandler =
|
||||
| _ when level |> List.contains User && isUserLoggedOn ctx -> return! next ctx
|
||||
| _ when level |> List.contains Group && isGroupLoggedOn ctx -> return! next ctx
|
||||
| _ when level |> List.contains Admin && isUserLoggedOn ctx ->
|
||||
match (currentUser ctx).isAdmin with
|
||||
match (currentUser ctx).IsAdmin with
|
||||
| true -> return! next ctx
|
||||
| false ->
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
|
@ -22,9 +22,9 @@ let getConnection () = task {
|
||||
/// Create a mail message object, filled with everything but the body content
|
||||
let createMessage (grp : SmallGroup) subj =
|
||||
let msg = new MimeMessage ()
|
||||
msg.From.Add (MailboxAddress (grp.preferences.emailFromName, fromAddress))
|
||||
msg.From.Add (MailboxAddress (grp.Preferences.EmailFromName, fromAddress))
|
||||
msg.Subject <- subj
|
||||
msg.ReplyTo.Add (MailboxAddress (grp.preferences.emailFromName, grp.preferences.emailFromAddress))
|
||||
msg.ReplyTo.Add (MailboxAddress (grp.Preferences.EmailFromName, grp.Preferences.EmailFromAddress))
|
||||
msg
|
||||
|
||||
/// Create an HTML-format e-mail message
|
||||
@ -63,12 +63,8 @@ let sendEmails (client : SmtpClient) (recipients : Member list) grp subj html te
|
||||
use plainTextMsg = createTextMessage grp subj text s
|
||||
|
||||
for mbr in recipients do
|
||||
let emailType =
|
||||
match mbr.format with
|
||||
| Some f -> EmailFormat.fromCode f
|
||||
| None -> grp.preferences.defaultEmailType
|
||||
let emailTo = MailboxAddress (mbr.memberName, mbr.email)
|
||||
match emailType with
|
||||
let emailTo = MailboxAddress (mbr.Name, mbr.Email)
|
||||
match defaultArg mbr.Format grp.Preferences.DefaultEmailType with
|
||||
| HtmlFormat ->
|
||||
htmlMsg.To.Add emailTo
|
||||
let! _ = client.SendAsync htmlMsg
|
||||
|
@ -12,7 +12,7 @@ open PrayerTracker.ViewModels
|
||||
/// Retrieve a prayer request, and ensure that it belongs to the current class
|
||||
let private findRequest (ctx : HttpContext) reqId = task {
|
||||
match! ctx.db.TryRequestById reqId with
|
||||
| Some req when req.smallGroupId = (currentGroup ctx).smallGroupId -> return Ok req
|
||||
| Some req when req.SmallGroupId = (currentGroup ctx).Id -> return Ok req
|
||||
| Some _ ->
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
addError ctx s["The prayer request you tried to access is not assigned to your group"]
|
||||
@ -27,12 +27,12 @@ let private generateRequestList ctx date = task {
|
||||
let listDate = match date with Some d -> d | None -> grp.localDateNow clock
|
||||
let! reqs = ctx.db.AllRequestsForSmallGroup grp clock (Some listDate) true 0
|
||||
return
|
||||
{ Requests = reqs
|
||||
Date = listDate
|
||||
SmallGroup = grp
|
||||
ShowHeader = true
|
||||
CanEmail = Option.isSome ctx.Session.user
|
||||
Recipients = []
|
||||
{ Requests = reqs
|
||||
Date = listDate
|
||||
SmallGroup = grp
|
||||
ShowHeader = true
|
||||
CanEmail = Option.isSome ctx.Session.user
|
||||
Recipients = []
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,20 +44,21 @@ let private parseListDate (date : string option) =
|
||||
|
||||
|
||||
/// GET /prayer-request/[request-id]/edit
|
||||
let edit (reqId : PrayerRequestId) : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task {
|
||||
let edit reqId : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task {
|
||||
let startTicks = DateTime.Now.Ticks
|
||||
let grp = currentGroup ctx
|
||||
let now = grp.localDateNow (ctx.GetService<IClock> ())
|
||||
if reqId = Guid.Empty then
|
||||
let requestId = PrayerRequestId reqId
|
||||
if requestId.Value = Guid.Empty then
|
||||
return!
|
||||
{ viewInfo ctx startTicks with Script = [ "ckeditor/ckeditor" ]; HelpLink = Some Help.editRequest }
|
||||
|> Views.PrayerRequest.edit EditRequest.empty (now.ToString "yyyy-MM-dd") ctx
|
||||
|> renderHtml next ctx
|
||||
else
|
||||
match! findRequest ctx reqId with
|
||||
match! findRequest ctx requestId with
|
||||
| Ok req ->
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
if req.isExpired now grp.preferences.daysToExpire then
|
||||
if req.isExpired now grp.Preferences.DaysToExpire then
|
||||
{ UserMessage.warning with
|
||||
Text = htmlLocString s["This request is expired."]
|
||||
Description =
|
||||
@ -81,10 +82,10 @@ let email date : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task {
|
||||
let listDate = parseListDate (Some date)
|
||||
let grp = currentGroup ctx
|
||||
let! list = generateRequestList ctx listDate
|
||||
let! recipients = ctx.db.AllMembersForSmallGroup grp.smallGroupId
|
||||
let! recipients = ctx.db.AllMembersForSmallGroup grp.Id
|
||||
use! client = Email.getConnection ()
|
||||
do! Email.sendEmails client recipients
|
||||
grp s["Prayer Requests for {0} - {1:MMMM d, yyyy}", grp.name, list.Date].Value
|
||||
grp s["Prayer Requests for {0} - {1:MMMM d, yyyy}", grp.Name, list.Date].Value
|
||||
(list.AsHtml s) (list.AsText s) s
|
||||
return!
|
||||
viewInfo ctx startTicks
|
||||
@ -95,7 +96,8 @@ let email date : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task {
|
||||
|
||||
/// POST /prayer-request/[request-id]/delete
|
||||
let delete reqId : HttpHandler = requireAccess [ User ] >=> validateCsrf >=> fun next ctx -> task {
|
||||
match! findRequest ctx reqId with
|
||||
let requestId = PrayerRequestId reqId
|
||||
match! findRequest ctx requestId with
|
||||
| Ok req ->
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
ctx.db.PrayerRequests.Remove req |> ignore
|
||||
@ -108,10 +110,11 @@ let delete reqId : HttpHandler = requireAccess [ User ] >=> validateCsrf >=> fun
|
||||
|
||||
/// GET /prayer-request/[request-id]/expire
|
||||
let expire reqId : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task {
|
||||
match! findRequest ctx reqId with
|
||||
let requestId = PrayerRequestId reqId
|
||||
match! findRequest ctx requestId with
|
||||
| Ok req ->
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
ctx.db.UpdateEntry { req with expiration = Forced }
|
||||
ctx.db.UpdateEntry { req with Expiration = Forced }
|
||||
let! _ = ctx.db.SaveChangesAsync ()
|
||||
addInfo ctx s["Successfully {0} prayer request", s["Expired"].Value.ToLower ()]
|
||||
return! redirectTo false "/prayer-requests" next ctx
|
||||
@ -123,7 +126,7 @@ let expire reqId : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task
|
||||
let list groupId : HttpHandler = requireAccess [ AccessLevel.Public ] >=> fun next ctx -> task {
|
||||
let startTicks = DateTime.Now.Ticks
|
||||
match! ctx.db.TryGroupById groupId with
|
||||
| Some grp when grp.preferences.isPublic ->
|
||||
| Some grp when grp.Preferences.IsPublic ->
|
||||
let clock = ctx.GetService<IClock> ()
|
||||
let! reqs = ctx.db.AllRequestsForSmallGroup grp clock None true 0
|
||||
return!
|
||||
@ -203,10 +206,11 @@ let print date : HttpHandler = requireAccess [ User; Group ] >=> fun next ctx ->
|
||||
|
||||
/// GET /prayer-request/[request-id]/restore
|
||||
let restore reqId : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task {
|
||||
match! findRequest ctx reqId with
|
||||
let requestId = PrayerRequestId reqId
|
||||
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 = DateTime.Now }
|
||||
let! _ = ctx.db.SaveChangesAsync ()
|
||||
addInfo ctx s["Successfully {0} prayer request", s["Restored"].Value.ToLower ()]
|
||||
return! redirectTo false "/prayer-requests" next ctx
|
||||
@ -219,16 +223,16 @@ let save : HttpHandler = requireAccess [ User ] >=> validateCsrf >=> fun next ct
|
||||
match! ctx.TryBindFormAsync<EditRequest> () with
|
||||
| Ok m ->
|
||||
let! req =
|
||||
if m.IsNew then Task.FromResult (Some { PrayerRequest.empty with prayerRequestId = Guid.NewGuid () })
|
||||
else ctx.db.TryRequestById m.RequestId
|
||||
if m.IsNew then Task.FromResult (Some { PrayerRequest.empty with Id = (Guid.NewGuid >> PrayerRequestId) () })
|
||||
else ctx.db.TryRequestById (idFromShort PrayerRequestId m.RequestId)
|
||||
match req with
|
||||
| Some pr ->
|
||||
let upd8 =
|
||||
{ pr with
|
||||
requestType = PrayerRequestType.fromCode m.RequestType
|
||||
requestor = match m.Requestor with Some x when x.Trim () = "" -> None | x -> x
|
||||
text = ckEditorToText m.Text
|
||||
expiration = Expiration.fromCode m.Expiration
|
||||
RequestType = PrayerRequestType.fromCode m.RequestType
|
||||
Requestor = match m.Requestor with Some x when x.Trim () = "" -> None | x -> x
|
||||
Text = ckEditorToText m.Text
|
||||
Expiration = Expiration.fromCode m.Expiration
|
||||
}
|
||||
let grp = currentGroup ctx
|
||||
let now = grp.localDateNow (ctx.GetService<IClock> ())
|
||||
@ -236,13 +240,13 @@ let save : HttpHandler = requireAccess [ User ] >=> validateCsrf >=> fun next ct
|
||||
| true ->
|
||||
let dt = defaultArg m.EnteredDate now
|
||||
{ upd8 with
|
||||
smallGroupId = grp.smallGroupId
|
||||
userId = (currentUser ctx).userId
|
||||
enteredDate = dt
|
||||
updatedDate = dt
|
||||
SmallGroupId = grp.Id
|
||||
UserId = (currentUser ctx).Id
|
||||
EnteredDate = dt
|
||||
UpdatedDate = dt
|
||||
}
|
||||
| false when defaultArg m.SkipDateUpdate false -> upd8
|
||||
| false -> { upd8 with updatedDate = now }
|
||||
| false -> { upd8 with UpdatedDate = now }
|
||||
|> if m.IsNew then ctx.db.AddEntry else ctx.db.UpdateEntry
|
||||
let! _ = ctx.db.SaveChangesAsync ()
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
|
@ -1,34 +1,29 @@
|
||||
module PrayerTracker.Handlers.SmallGroup
|
||||
|
||||
open System
|
||||
open Giraffe
|
||||
open Giraffe.ViewEngine
|
||||
open Microsoft.AspNetCore.Http
|
||||
open NodaTime
|
||||
open PrayerTracker
|
||||
open PrayerTracker.Cookies
|
||||
open PrayerTracker.Entities
|
||||
open PrayerTracker.ViewModels
|
||||
open PrayerTracker.Views.CommonFunctions
|
||||
open System
|
||||
open System.Threading.Tasks
|
||||
|
||||
/// Set a small group "Remember Me" cookie
|
||||
let private setGroupCookie (ctx : HttpContext) pwHash =
|
||||
ctx.Response.Cookies.Append
|
||||
(Key.Cookie.group, { GroupId = (currentGroup ctx).smallGroupId; PasswordHash = pwHash }.toPayload (),
|
||||
(Key.Cookie.group, { GroupId = (currentGroup ctx).Id.Value; PasswordHash = pwHash }.toPayload (),
|
||||
autoRefresh)
|
||||
|
||||
|
||||
/// GET /small-group/announcement
|
||||
let announcement : HttpHandler = requireAccess [ User ] >=> fun next ctx ->
|
||||
{ viewInfo ctx DateTime.Now.Ticks with HelpLink = Some Help.sendAnnouncement; Script = [ "ckeditor/ckeditor" ] }
|
||||
|> Views.SmallGroup.announcement (currentUser ctx).isAdmin ctx
|
||||
|> Views.SmallGroup.announcement (currentUser ctx).IsAdmin ctx
|
||||
|> renderHtml next ctx
|
||||
|
||||
|
||||
/// POST /small-group/[group-id]/delete
|
||||
let delete groupId : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next ctx -> task {
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
let delete grpId : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next ctx -> task {
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
let groupId = SmallGroupId grpId
|
||||
match! ctx.db.TryGroupById groupId with
|
||||
| Some grp ->
|
||||
let! reqs = ctx.db.CountRequestsBySmallGroup groupId
|
||||
@ -37,31 +32,31 @@ let delete groupId : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=>
|
||||
let! _ = ctx.db.SaveChangesAsync ()
|
||||
addInfo ctx
|
||||
s["The group {0} and its {1} prayer request(s) were deleted successfully; revoked access from {2} user(s)",
|
||||
grp.name, reqs, users]
|
||||
grp.Name, reqs, users]
|
||||
return! redirectTo false "/small-groups" next ctx
|
||||
| None -> return! fourOhFour next ctx
|
||||
}
|
||||
|
||||
|
||||
/// POST /small-group/member/[member-id]/delete
|
||||
let deleteMember memberId : HttpHandler = requireAccess [ User ] >=> validateCsrf >=> fun next ctx -> task {
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
let deleteMember mbrId : HttpHandler = requireAccess [ User ] >=> validateCsrf >=> fun next ctx -> task {
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
let memberId = MemberId mbrId
|
||||
match! ctx.db.TryMemberById memberId with
|
||||
| Some mbr when mbr.smallGroupId = (currentGroup ctx).smallGroupId ->
|
||||
| Some mbr when mbr.SmallGroupId = (currentGroup ctx).Id ->
|
||||
ctx.db.RemoveEntry mbr
|
||||
let! _ = ctx.db.SaveChangesAsync ()
|
||||
addHtmlInfo ctx s["The group member “{0}” was deleted successfully", mbr.memberName]
|
||||
addHtmlInfo ctx s["The group member “{0}” was deleted successfully", mbr.Name]
|
||||
return! redirectTo false "/small-group/members" next ctx
|
||||
| Some _
|
||||
| None -> return! fourOhFour next ctx
|
||||
}
|
||||
|
||||
|
||||
/// GET /small-group/[group-id]/edit
|
||||
let edit (groupId : SmallGroupId) : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> task {
|
||||
let edit grpId : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> task {
|
||||
let startTicks = DateTime.Now.Ticks
|
||||
let! churches = ctx.db.AllChurches ()
|
||||
if groupId = Guid.Empty then
|
||||
let groupId = SmallGroupId grpId
|
||||
if groupId.Value = Guid.Empty then
|
||||
return!
|
||||
viewInfo ctx startTicks
|
||||
|> Views.SmallGroup.edit EditSmallGroup.empty churches ctx
|
||||
@ -76,21 +71,21 @@ let edit (groupId : SmallGroupId) : HttpHandler = requireAccess [ Admin ] >=> fu
|
||||
| None -> return! fourOhFour next ctx
|
||||
}
|
||||
|
||||
|
||||
/// GET /small-group/member/[member-id]/edit
|
||||
let editMember (memberId : MemberId) : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task {
|
||||
let editMember mbrId : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task {
|
||||
let startTicks = DateTime.Now.Ticks
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
let grp = currentGroup ctx
|
||||
let types = ReferenceList.emailTypeList grp.preferences.defaultEmailType s
|
||||
if memberId = Guid.Empty then
|
||||
let types = ReferenceList.emailTypeList grp.Preferences.DefaultEmailType s
|
||||
let memberId = MemberId mbrId
|
||||
if memberId.Value = Guid.Empty then
|
||||
return!
|
||||
viewInfo ctx startTicks
|
||||
|> Views.SmallGroup.editMember EditMember.empty types ctx
|
||||
|> renderHtml next ctx
|
||||
else
|
||||
match! ctx.db.TryMemberById memberId with
|
||||
| Some mbr when mbr.smallGroupId = grp.smallGroupId ->
|
||||
| Some mbr when mbr.SmallGroupId = grp.Id ->
|
||||
return!
|
||||
viewInfo ctx startTicks
|
||||
|> Views.SmallGroup.editMember (EditMember.fromMember mbr) types ctx
|
||||
@ -99,25 +94,23 @@ let editMember (memberId : MemberId) : HttpHandler = requireAccess [ User ] >=>
|
||||
| None -> return! fourOhFour next ctx
|
||||
}
|
||||
|
||||
|
||||
/// GET /small-group/log-on/[group-id?]
|
||||
let logOn (groupId : SmallGroupId option) : HttpHandler = requireAccess [ AccessLevel.Public ] >=> fun next ctx -> task {
|
||||
let logOn grpId : HttpHandler = requireAccess [ AccessLevel.Public ] >=> fun next ctx -> task {
|
||||
let startTicks = DateTime.Now.Ticks
|
||||
let! groups = ctx.db.ProtectedGroups ()
|
||||
let grpId = match groupId with Some gid -> flatGuid gid | None -> ""
|
||||
let groupId = match grpId with Some gid -> shortGuid gid | None -> ""
|
||||
return!
|
||||
{ viewInfo ctx startTicks with HelpLink = Some Help.logOn }
|
||||
|> Views.SmallGroup.logOn groups grpId ctx
|
||||
|> Views.SmallGroup.logOn groups groupId ctx
|
||||
|> renderHtml next ctx
|
||||
}
|
||||
|
||||
|
||||
/// POST /small-group/log-on/submit
|
||||
let logOnSubmit : HttpHandler = requireAccess [ AccessLevel.Public ] >=> validateCsrf >=> fun next ctx -> task {
|
||||
match! ctx.TryBindFormAsync<GroupLogOn> () with
|
||||
| Ok m ->
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
match! ctx.db.TryGroupLogOnByPassword m.SmallGroupId m.Password with
|
||||
match! ctx.db.TryGroupLogOnByPassword (idFromShort SmallGroupId m.SmallGroupId) m.Password with
|
||||
| Some grp ->
|
||||
ctx.Session.smallGroup <- Some grp
|
||||
if defaultArg m.RememberMe false then (setGroupCookie ctx << sha1Hash) m.Password
|
||||
@ -125,11 +118,10 @@ let logOnSubmit : HttpHandler = requireAccess [ AccessLevel.Public ] >=> validat
|
||||
return! redirectTo false "/prayer-requests/view" next ctx
|
||||
| None ->
|
||||
addError ctx s["Password incorrect - login unsuccessful"]
|
||||
return! redirectTo false $"/small-group/log-on/{flatGuid m.SmallGroupId}" next ctx
|
||||
return! redirectTo false $"/small-group/log-on/{m.SmallGroupId}" next ctx
|
||||
| Result.Error e -> return! bindError e next ctx
|
||||
}
|
||||
|
||||
|
||||
/// GET /small-groups
|
||||
let maintain : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> task {
|
||||
let startTicks = DateTime.Now.Ticks
|
||||
@ -140,28 +132,28 @@ let maintain : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> task {
|
||||
|> renderHtml next ctx
|
||||
}
|
||||
|
||||
|
||||
/// GET /small-group/members
|
||||
let members : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task {
|
||||
let startTicks = DateTime.Now.Ticks
|
||||
let grp = currentGroup ctx
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
let! members = ctx.db.AllMembersForSmallGroup grp.smallGroupId
|
||||
let types = ReferenceList.emailTypeList grp.preferences.defaultEmailType s |> Map.ofSeq
|
||||
let! members = ctx.db.AllMembersForSmallGroup grp.Id
|
||||
let types = ReferenceList.emailTypeList grp.Preferences.DefaultEmailType s |> Map.ofSeq
|
||||
return!
|
||||
{ viewInfo ctx startTicks with HelpLink = Some Help.maintainGroupMembers }
|
||||
|> Views.SmallGroup.members members types ctx
|
||||
|> renderHtml next ctx
|
||||
}
|
||||
|
||||
open NodaTime
|
||||
|
||||
/// GET /small-group
|
||||
let overview : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task {
|
||||
let startTicks = DateTime.Now.Ticks
|
||||
let clock = ctx.GetService<IClock> ()
|
||||
let! reqs = ctx.db.AllRequestsForSmallGroup (currentGroup ctx) clock None true 0
|
||||
let! reqCount = ctx.db.CountRequestsBySmallGroup (currentGroup ctx).smallGroupId
|
||||
let! mbrCount = ctx.db.CountMembersForSmallGroup (currentGroup ctx).smallGroupId
|
||||
let! reqCount = ctx.db.CountRequestsBySmallGroup (currentGroup ctx).Id
|
||||
let! mbrCount = ctx.db.CountMembersForSmallGroup (currentGroup ctx).Id
|
||||
let m =
|
||||
{ TotalActiveReqs = List.length reqs
|
||||
AllReqs = reqCount
|
||||
@ -169,9 +161,9 @@ let overview : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task {
|
||||
ActiveReqsByType =
|
||||
(reqs
|
||||
|> Seq.ofList
|
||||
|> Seq.map (fun req -> req.requestType)
|
||||
|> Seq.map (fun req -> req.RequestType)
|
||||
|> Seq.distinct
|
||||
|> Seq.map (fun reqType -> reqType, reqs |> List.filter (fun r -> r.requestType = reqType) |> List.length)
|
||||
|> Seq.map (fun reqType -> reqType, reqs |> List.filter (fun r -> r.RequestType = reqType) |> List.length)
|
||||
|> Map.ofSeq)
|
||||
}
|
||||
return!
|
||||
@ -180,17 +172,17 @@ let overview : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task {
|
||||
|> renderHtml next ctx
|
||||
}
|
||||
|
||||
|
||||
/// GET /small-group/preferences
|
||||
let preferences : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task {
|
||||
let startTicks = DateTime.Now.Ticks
|
||||
let! tzs = ctx.db.AllTimeZones ()
|
||||
return!
|
||||
{ viewInfo ctx startTicks with HelpLink = Some Help.groupPreferences }
|
||||
|> Views.SmallGroup.preferences (EditPreferences.fromPreferences (currentGroup ctx).preferences) tzs ctx
|
||||
|> Views.SmallGroup.preferences (EditPreferences.fromPreferences (currentGroup ctx).Preferences) tzs ctx
|
||||
|> renderHtml next ctx
|
||||
}
|
||||
|
||||
open System.Threading.Tasks
|
||||
|
||||
/// POST /small-group/save
|
||||
let save : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next ctx -> task {
|
||||
@ -198,15 +190,15 @@ let save : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next c
|
||||
| Ok m ->
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
let! group =
|
||||
if m.IsNew then Task.FromResult (Some { SmallGroup.empty with smallGroupId = Guid.NewGuid () })
|
||||
else ctx.db.TryGroupById m.SmallGroupId
|
||||
if m.IsNew then Task.FromResult (Some { SmallGroup.empty with Id = (Guid.NewGuid >> SmallGroupId) () })
|
||||
else ctx.db.TryGroupById (idFromShort SmallGroupId m.SmallGroupId)
|
||||
match group with
|
||||
| Some grp ->
|
||||
m.populateGroup grp
|
||||
|> function
|
||||
| grp when m.IsNew ->
|
||||
ctx.db.AddEntry grp
|
||||
ctx.db.AddEntry { grp.preferences with smallGroupId = grp.smallGroupId }
|
||||
ctx.db.AddEntry { grp.Preferences with SmallGroupId = grp.Id }
|
||||
| grp -> ctx.db.UpdateEntry grp
|
||||
let! _ = ctx.db.SaveChangesAsync ()
|
||||
let act = s[if m.IsNew then "Added" else "Updated"].Value.ToLower ()
|
||||
@ -216,27 +208,26 @@ let save : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next c
|
||||
| Result.Error e -> return! bindError e next ctx
|
||||
}
|
||||
|
||||
|
||||
/// POST /small-group/member/save
|
||||
let saveMember : HttpHandler = requireAccess [ User ] >=> validateCsrf >=> fun next ctx -> task {
|
||||
match! ctx.TryBindFormAsync<EditMember> () with
|
||||
| Ok m ->
|
||||
| Ok model ->
|
||||
let grp = currentGroup ctx
|
||||
let! mMbr =
|
||||
if m.IsNew then
|
||||
Task.FromResult (Some { Member.empty with memberId = Guid.NewGuid (); smallGroupId = grp.smallGroupId })
|
||||
else ctx.db.TryMemberById m.MemberId
|
||||
if model.IsNew then
|
||||
Task.FromResult (Some { Member.empty with Id = (Guid.NewGuid >> MemberId) (); SmallGroupId = grp.Id })
|
||||
else ctx.db.TryMemberById (idFromShort MemberId model.MemberId)
|
||||
match mMbr with
|
||||
| Some mbr when mbr.smallGroupId = grp.smallGroupId ->
|
||||
| Some mbr when mbr.SmallGroupId = grp.Id ->
|
||||
{ mbr with
|
||||
memberName = m.Name
|
||||
email = m.Email
|
||||
format = match m.Format with "" | null -> None | _ -> Some m.Format
|
||||
Name = model.Name
|
||||
Email = model.Email
|
||||
Format = match model.Format with "" | null -> None | _ -> Some (EmailFormat.fromCode model.Format)
|
||||
}
|
||||
|> if m.IsNew then ctx.db.AddEntry else ctx.db.UpdateEntry
|
||||
|> if model.IsNew then ctx.db.AddEntry else ctx.db.UpdateEntry
|
||||
let! _ = ctx.db.SaveChangesAsync ()
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
let act = s[if m.IsNew then "Added" else "Updated"].Value.ToLower ()
|
||||
let act = s[if model.IsNew then "Added" else "Updated"].Value.ToLower ()
|
||||
addInfo ctx s["Successfully {0} group member", act]
|
||||
return! redirectTo false "/small-group/members" next ctx
|
||||
| Some _
|
||||
@ -244,7 +235,6 @@ let saveMember : HttpHandler = requireAccess [ User ] >=> validateCsrf >=> fun n
|
||||
| Result.Error e -> return! bindError e next ctx
|
||||
}
|
||||
|
||||
|
||||
/// POST /small-group/preferences/save
|
||||
let savePreferences : HttpHandler = requireAccess [ User ] >=> validateCsrf >=> fun next ctx -> task {
|
||||
match! ctx.TryBindFormAsync<EditPreferences> () with
|
||||
@ -252,13 +242,13 @@ let savePreferences : HttpHandler = requireAccess [ User ] >=> validateCsrf >=>
|
||||
// Since the class is stored in the session, we'll use an intermediate instance to persist it; once that works,
|
||||
// we can repopulate the session instance. That way, if the update fails, the page should still show the
|
||||
// database values, not the then out-of-sync session ones.
|
||||
match! ctx.db.TryGroupById (currentGroup ctx).smallGroupId with
|
||||
match! ctx.db.TryGroupById (currentGroup ctx).Id with
|
||||
| Some grp ->
|
||||
let prefs = m.PopulatePreferences grp.preferences
|
||||
let prefs = m.PopulatePreferences grp.Preferences
|
||||
ctx.db.UpdateEntry prefs
|
||||
let! _ = ctx.db.SaveChangesAsync ()
|
||||
// Refresh session instance
|
||||
ctx.Session.smallGroup <- Some { grp with preferences = prefs }
|
||||
ctx.Session.smallGroup <- Some { grp with Preferences = prefs }
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
addInfo ctx s["Group preferences updated successfully"]
|
||||
return! redirectTo false "/small-group/preferences" next ctx
|
||||
@ -266,6 +256,8 @@ let savePreferences : HttpHandler = requireAccess [ User ] >=> validateCsrf >=>
|
||||
| Result.Error e -> return! bindError e next ctx
|
||||
}
|
||||
|
||||
open Giraffe.ViewEngine
|
||||
open PrayerTracker.Views.CommonFunctions
|
||||
|
||||
/// POST /small-group/announcement/send
|
||||
let sendAnnouncement : HttpHandler = requireAccess [ User ] >=> validateCsrf >=> fun next ctx -> task {
|
||||
@ -279,18 +271,18 @@ let sendAnnouncement : HttpHandler = requireAccess [ User ] >=> validateCsrf >=>
|
||||
// Reformat the text to use the class's font stylings
|
||||
let requestText = ckEditorToText m.Text
|
||||
let htmlText =
|
||||
p [ _style $"font-family:{grp.preferences.listFonts};font-size:%d{grp.preferences.textFontSize}pt;" ]
|
||||
p [ _style $"font-family:{grp.Preferences.Fonts};font-size:%d{grp.Preferences.TextFontSize}pt;" ]
|
||||
[ rawText requestText ]
|
||||
|> renderHtmlNode
|
||||
let plainText = (htmlToPlainText >> wordWrap 74) htmlText
|
||||
// Send the e-mails
|
||||
let! recipients =
|
||||
match m.SendToClass with
|
||||
| "N" when usr.isAdmin -> ctx.db.AllUsersAsMembers ()
|
||||
| _ -> ctx.db.AllMembersForSmallGroup grp.smallGroupId
|
||||
| "N" when usr.IsAdmin -> ctx.db.AllUsersAsMembers ()
|
||||
| _ -> ctx.db.AllMembersForSmallGroup grp.Id
|
||||
use! client = Email.getConnection ()
|
||||
do! Email.sendEmails client recipients grp
|
||||
s["Announcement for {0} - {1:MMMM d, yyyy} {2}", grp.name, now.Date,
|
||||
s["Announcement for {0} - {1:MMMM d, yyyy} {2}", grp.Name, now.Date,
|
||||
(now.ToString "h:mm tt").ToLower ()].Value
|
||||
htmlText plainText s
|
||||
// Add to the request list if desired
|
||||
@ -300,13 +292,13 @@ let sendAnnouncement : HttpHandler = requireAccess [ User ] >=> validateCsrf >=>
|
||||
| _, Some x when not x -> ()
|
||||
| _, _ ->
|
||||
{ PrayerRequest.empty with
|
||||
prayerRequestId = Guid.NewGuid ()
|
||||
smallGroupId = grp.smallGroupId
|
||||
userId = usr.userId
|
||||
requestType = (Option.get >> PrayerRequestType.fromCode) m.RequestType
|
||||
text = requestText
|
||||
enteredDate = now
|
||||
updatedDate = now
|
||||
Id = (Guid.NewGuid >> PrayerRequestId) ()
|
||||
SmallGroupId = grp.Id
|
||||
UserId = usr.Id
|
||||
RequestType = (Option.get >> PrayerRequestType.fromCode) m.RequestType
|
||||
Text = requestText
|
||||
EnteredDate = now
|
||||
UpdatedDate = now
|
||||
}
|
||||
|> ctx.db.AddEntry
|
||||
let! _ = ctx.db.SaveChangesAsync ()
|
||||
|
@ -1,47 +1,45 @@
|
||||
module PrayerTracker.Handlers.User
|
||||
|
||||
open System
|
||||
open System.Collections.Generic
|
||||
open System.Net
|
||||
open System.Threading.Tasks
|
||||
open Giraffe
|
||||
open Microsoft.AspNetCore.Html
|
||||
open Microsoft.AspNetCore.Http
|
||||
open PrayerTracker
|
||||
open PrayerTracker.Cookies
|
||||
open PrayerTracker.Entities
|
||||
open PrayerTracker.ViewModels
|
||||
open PrayerTracker.Views.CommonFunctions
|
||||
|
||||
/// Set the user's "remember me" cookie
|
||||
let private setUserCookie (ctx : HttpContext) pwHash =
|
||||
ctx.Response.Cookies.Append (
|
||||
Key.Cookie.user,
|
||||
{ Id = (currentUser ctx).userId; GroupId = (currentGroup ctx).smallGroupId; PasswordHash = pwHash }.toPayload (),
|
||||
{ Id = (currentUser ctx).Id.Value; GroupId = (currentGroup ctx).Id.Value; PasswordHash = pwHash }.toPayload (),
|
||||
autoRefresh)
|
||||
|
||||
open System
|
||||
open System.Collections.Generic
|
||||
|
||||
/// Retrieve a user from the database by password
|
||||
// If the hashes do not match, determine if it matches a previous scheme, and upgrade them if it does
|
||||
let private findUserByPassword m (db : AppDbContext) = task {
|
||||
match! db.TryUserByEmailAndGroup m.Email m.SmallGroupId with
|
||||
| Some u when Option.isSome u.salt ->
|
||||
let private findUserByPassword model (db : AppDbContext) = task {
|
||||
match! db.TryUserByEmailAndGroup model.Email (idFromShort SmallGroupId model.SmallGroupId) with
|
||||
| Some u when Option.isSome u.Salt ->
|
||||
// Already upgraded; match = success
|
||||
let pwHash = pbkdf2Hash (Option.get u.salt) m.Password
|
||||
if u.passwordHash = pwHash then
|
||||
return Some { u with passwordHash = ""; salt = None; smallGroups = List<UserSmallGroup>() }, pwHash
|
||||
let pwHash = pbkdf2Hash (Option.get u.Salt) model.Password
|
||||
if u.PasswordHash = pwHash then
|
||||
return Some { u with PasswordHash = ""; Salt = None; SmallGroups = List<UserSmallGroup>() }, pwHash
|
||||
else return None, ""
|
||||
| Some u when u.passwordHash = sha1Hash m.Password ->
|
||||
| Some u when u.PasswordHash = sha1Hash model.Password ->
|
||||
// Not upgraded, but password is good; upgrade 'em!
|
||||
// Upgrade 'em!
|
||||
let salt = Guid.NewGuid ()
|
||||
let pwHash = pbkdf2Hash salt m.Password
|
||||
let upgraded = { u with salt = Some salt; passwordHash = pwHash }
|
||||
let pwHash = pbkdf2Hash salt model.Password
|
||||
let upgraded = { u with Salt = Some salt; PasswordHash = pwHash }
|
||||
db.UpdateEntry upgraded
|
||||
let! _ = db.SaveChangesAsync ()
|
||||
return Some { u with passwordHash = ""; salt = None; smallGroups = List<UserSmallGroup>() }, pwHash
|
||||
return Some { u with PasswordHash = ""; Salt = None; SmallGroups = List<UserSmallGroup>() }, pwHash
|
||||
| _ -> return None, ""
|
||||
}
|
||||
|
||||
open System.Threading.Tasks
|
||||
|
||||
/// POST /user/password/change
|
||||
let changePassword : HttpHandler = requireAccess [ User ] >=> validateCsrf >=> fun next ctx -> task {
|
||||
@ -49,13 +47,13 @@ let changePassword : HttpHandler = requireAccess [ User ] >=> validateCsrf >=> f
|
||||
| Ok m ->
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
let curUsr = currentUser ctx
|
||||
let! dbUsr = ctx.db.TryUserById curUsr.userId
|
||||
let! dbUsr = ctx.db.TryUserById curUsr.Id
|
||||
let! user =
|
||||
match dbUsr with
|
||||
| Some usr ->
|
||||
// Check the old password against a possibly non-salted hash
|
||||
(match usr.salt with Some salt -> pbkdf2Hash salt | None -> sha1Hash) m.OldPassword
|
||||
|> ctx.db.TryUserLogOnByCookie curUsr.userId (currentGroup ctx).smallGroupId
|
||||
(match usr.Salt with Some salt -> pbkdf2Hash salt | None -> sha1Hash) m.OldPassword
|
||||
|> ctx.db.TryUserLogOnByCookie curUsr.Id (currentGroup ctx).Id
|
||||
| _ -> Task.FromResult None
|
||||
match user with
|
||||
| Some _ when m.NewPassword = m.NewPasswordConfirm ->
|
||||
@ -63,10 +61,10 @@ let changePassword : HttpHandler = requireAccess [ User ] >=> validateCsrf >=> f
|
||||
| Some usr ->
|
||||
// Generate new salt whenever the password is changed
|
||||
let salt = Guid.NewGuid ()
|
||||
ctx.db.UpdateEntry { usr with passwordHash = pbkdf2Hash salt m.NewPassword; salt = Some salt }
|
||||
ctx.db.UpdateEntry { usr with PasswordHash = pbkdf2Hash salt m.NewPassword; Salt = Some salt }
|
||||
let! _ = ctx.db.SaveChangesAsync ()
|
||||
// If the user is remembered, update the cookie with the new hash
|
||||
if ctx.Request.Cookies.Keys.Contains Key.Cookie.user then setUserCookie ctx usr.passwordHash
|
||||
if ctx.Request.Cookies.Keys.Contains Key.Cookie.user then setUserCookie ctx usr.PasswordHash
|
||||
addInfo ctx s["Your password was changed successfully"]
|
||||
| None -> addError ctx s["Unable to change password"]
|
||||
return! redirectTo false "/" next ctx
|
||||
@ -79,9 +77,9 @@ let changePassword : HttpHandler = requireAccess [ User ] >=> validateCsrf >=> f
|
||||
| Result.Error e -> return! bindError e next ctx
|
||||
}
|
||||
|
||||
|
||||
/// POST /user/[user-id]/delete
|
||||
let delete userId : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next ctx -> task {
|
||||
let delete usrId : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next ctx -> task {
|
||||
let userId = UserId usrId
|
||||
match! ctx.db.TryUserById userId with
|
||||
| Some user ->
|
||||
ctx.db.RemoveEntry user
|
||||
@ -92,33 +90,36 @@ let delete userId : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> f
|
||||
| _ -> return! fourOhFour next ctx
|
||||
}
|
||||
|
||||
open System.Net
|
||||
open Microsoft.AspNetCore.Html
|
||||
|
||||
/// POST /user/log-on
|
||||
let doLogOn : HttpHandler = requireAccess [ AccessLevel.Public ] >=> validateCsrf >=> fun next ctx -> task {
|
||||
match! ctx.TryBindFormAsync<UserLogOn> () with
|
||||
| Ok m ->
|
||||
| Ok model ->
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
let! usr, pwHash = findUserByPassword m ctx.db
|
||||
let! grp = ctx.db.TryGroupById m.SmallGroupId
|
||||
let! usr, pwHash = findUserByPassword model ctx.db
|
||||
let! grp = ctx.db.TryGroupById (idFromShort SmallGroupId model.SmallGroupId)
|
||||
let nextUrl =
|
||||
match usr with
|
||||
| Some _ ->
|
||||
ctx.Session.user <- usr
|
||||
ctx.Session.smallGroup <- grp
|
||||
if defaultArg m.RememberMe false then setUserCookie ctx pwHash
|
||||
if defaultArg model.RememberMe false then setUserCookie ctx pwHash
|
||||
addHtmlInfo ctx s["Log On Successful • Welcome to {0}", s["PrayerTracker"]]
|
||||
match m.RedirectUrl with
|
||||
match model.RedirectUrl with
|
||||
| None -> "/small-group"
|
||||
// TODO: ensure "x" is a local URL
|
||||
| Some x when x = "" -> "/small-group"
|
||||
| Some x -> x
|
||||
| _ ->
|
||||
let grpName = match grp with Some g -> g.name | _ -> "N/A"
|
||||
let grpName = match grp with Some g -> g.Name | _ -> "N/A"
|
||||
{ UserMessage.error with
|
||||
Text = htmlLocString s["Invalid credentials - log on unsuccessful"]
|
||||
Description =
|
||||
[ s["This is likely due to one of the following reasons"].Value
|
||||
":<ul><li>"
|
||||
s["The e-mail address “{0}” is invalid.", WebUtility.HtmlEncode m.Email].Value
|
||||
s["The e-mail address “{0}” is invalid.", WebUtility.HtmlEncode model.Email].Value
|
||||
"</li><li>"
|
||||
s["The password entered does not match the password for the given e-mail address."].Value
|
||||
"</li><li>"
|
||||
@ -137,9 +138,10 @@ let doLogOn : HttpHandler = requireAccess [ AccessLevel.Public ] >=> validateCsr
|
||||
|
||||
|
||||
/// GET /user/[user-id]/edit
|
||||
let edit (userId : UserId) : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> task {
|
||||
let edit usrId : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> task {
|
||||
let startTicks = DateTime.Now.Ticks
|
||||
if userId = Guid.Empty then
|
||||
let userId = UserId usrId
|
||||
if userId.Value = Guid.Empty then
|
||||
return!
|
||||
viewInfo ctx startTicks
|
||||
|> Views.User.edit EditUser.empty ctx
|
||||
@ -196,22 +198,22 @@ let save : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next c
|
||||
match! ctx.TryBindFormAsync<EditUser> () with
|
||||
| Ok m ->
|
||||
let! user =
|
||||
if m.IsNew then Task.FromResult (Some { User.empty with userId = Guid.NewGuid () })
|
||||
else ctx.db.TryUserById m.UserId
|
||||
if m.IsNew then Task.FromResult (Some { User.empty with Id = (Guid.NewGuid >> UserId) () })
|
||||
else ctx.db.TryUserById (idFromShort UserId m.UserId)
|
||||
let saltedUser =
|
||||
match user with
|
||||
| Some u ->
|
||||
match u.salt with
|
||||
match u.Salt with
|
||||
| None when m.Password <> "" ->
|
||||
// Generate salt so that a new password hash can be generated
|
||||
Some { u with salt = Some (Guid.NewGuid ()) }
|
||||
Some { u with Salt = Some (Guid.NewGuid ()) }
|
||||
| _ ->
|
||||
// Leave the user with no salt, so prior hash can be validated/upgraded
|
||||
user
|
||||
| _ -> user
|
||||
match saltedUser with
|
||||
| Some u ->
|
||||
let updatedUser = m.PopulateUser u (pbkdf2Hash (Option.get u.salt))
|
||||
let updatedUser = m.PopulateUser u (pbkdf2Hash (Option.get u.Salt))
|
||||
updatedUser |> if m.IsNew then ctx.db.AddEntry else ctx.db.UpdateEntry
|
||||
let! _ = ctx.db.SaveChangesAsync ()
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
@ -225,7 +227,7 @@ let save : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next c
|
||||
|> Some
|
||||
}
|
||||
|> addUserMessage ctx
|
||||
return! redirectTo false $"/user/{flatGuid u.userId}/small-groups" next ctx
|
||||
return! redirectTo false $"/user/{shortGuid u.Id.Value}/small-groups" next ctx
|
||||
else
|
||||
addInfo ctx s["Successfully {0} user", s["Updated"].Value.ToLower ()]
|
||||
return! redirectTo false "/users" next ctx
|
||||
@ -237,30 +239,30 @@ let save : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next c
|
||||
/// POST /user/small-groups/save
|
||||
let saveGroups : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next ctx -> task {
|
||||
match! ctx.TryBindFormAsync<AssignGroups> () with
|
||||
| Ok m ->
|
||||
| Ok model ->
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
match Seq.length m.SmallGroups with
|
||||
match Seq.length model.SmallGroups with
|
||||
| 0 ->
|
||||
addError ctx s["You must select at least one group to assign"]
|
||||
return! redirectTo false $"/user/{flatGuid m.UserId}/small-groups" next ctx
|
||||
return! redirectTo false $"/user/{model.UserId}/small-groups" next ctx
|
||||
| _ ->
|
||||
match! ctx.db.TryUserByIdWithGroups m.UserId with
|
||||
match! ctx.db.TryUserByIdWithGroups (idFromShort UserId model.UserId) with
|
||||
| Some user ->
|
||||
let groups =
|
||||
m.SmallGroups.Split ','
|
||||
|> Array.map Guid.Parse
|
||||
model.SmallGroups.Split ','
|
||||
|> Array.map (idFromShort SmallGroupId)
|
||||
|> List.ofArray
|
||||
user.smallGroups
|
||||
|> Seq.filter (fun x -> not (groups |> List.exists (fun y -> y = x.smallGroupId)))
|
||||
user.SmallGroups
|
||||
|> Seq.filter (fun x -> not (groups |> List.exists (fun y -> y = x.SmallGroupId)))
|
||||
|> ctx.db.UserGroupXref.RemoveRange
|
||||
groups
|
||||
|> Seq.ofList
|
||||
|> Seq.filter (fun x -> not (user.smallGroups |> Seq.exists (fun y -> y.smallGroupId = x)))
|
||||
|> Seq.map (fun x -> { UserSmallGroup.empty with userId = user.userId; smallGroupId = x })
|
||||
|> Seq.filter (fun x -> not (user.SmallGroups |> Seq.exists (fun y -> y.SmallGroupId = x)))
|
||||
|> Seq.map (fun x -> { UserSmallGroup.empty with UserId = user.Id; SmallGroupId = x })
|
||||
|> List.ofSeq
|
||||
|> List.iter ctx.db.AddEntry
|
||||
let! _ = ctx.db.SaveChangesAsync ()
|
||||
addInfo ctx s["Successfully updated group permissions for {0}", m.UserName]
|
||||
addInfo ctx s["Successfully updated group permissions for {0}", model.UserName]
|
||||
return! redirectTo false "/users" next ctx
|
||||
| _ -> return! fourOhFour next ctx
|
||||
| Result.Error e -> return! bindError e next ctx
|
||||
@ -268,12 +270,13 @@ let saveGroups : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun
|
||||
|
||||
|
||||
/// GET /user/[user-id]/small-groups
|
||||
let smallGroups userId : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> task {
|
||||
let smallGroups usrId : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> task {
|
||||
let startTicks = DateTime.Now.Ticks
|
||||
let userId = UserId usrId
|
||||
match! ctx.db.TryUserByIdWithGroups userId with
|
||||
| Some user ->
|
||||
let! groups = ctx.db.GroupList ()
|
||||
let curGroups = user.smallGroups |> Seq.map (fun g -> flatGuid g.smallGroupId) |> List.ofSeq
|
||||
let curGroups = user.SmallGroups |> Seq.map (fun g -> shortGuid g.SmallGroupId.Value) |> List.ofSeq
|
||||
return!
|
||||
viewInfo ctx startTicks
|
||||
|> Views.User.assignGroups (AssignGroups.fromUser user) groups curGroups ctx
|
||||
|
Loading…
Reference in New Issue
Block a user