Version 8 #43
|
@ -52,6 +52,22 @@ module private Helpers =
|
||||||
SmallGroup = SmallGroup.empty
|
SmallGroup = SmallGroup.empty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Map a row to a Prayer Request instance
|
||||||
|
let mapToPrayerRequest (row : RowReader) =
|
||||||
|
{ Id = PrayerRequestId (row.uuid "id")
|
||||||
|
UserId = UserId (row.uuid "user_id")
|
||||||
|
SmallGroupId = SmallGroupId (row.uuid "small_group_id")
|
||||||
|
EnteredDate = row.fieldValue<Instant> "entered_date"
|
||||||
|
UpdatedDate = row.fieldValue<Instant> "updated_date"
|
||||||
|
Requestor = row.stringOrNone "requestor"
|
||||||
|
Text = row.string "request_text"
|
||||||
|
NotifyChaplain = row.bool "notify_chaplain"
|
||||||
|
RequestType = PrayerRequestType.fromCode (row.string "request_id")
|
||||||
|
Expiration = Expiration.fromCode (row.string "expiration")
|
||||||
|
User = User.empty
|
||||||
|
SmallGroup = SmallGroup.empty
|
||||||
|
}
|
||||||
|
|
||||||
/// Map a row to a Small Group instance
|
/// Map a row to a Small Group instance
|
||||||
let mapToSmallGroup (row : RowReader) =
|
let mapToSmallGroup (row : RowReader) =
|
||||||
{ Id = SmallGroupId (row.uuid "id")
|
{ Id = SmallGroupId (row.uuid "id")
|
||||||
|
@ -64,6 +80,14 @@ module private Helpers =
|
||||||
Users = ResizeArray ()
|
Users = ResizeArray ()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Map a row to a Small Group information set
|
||||||
|
let mapToSmallGroupInfo (row : RowReader) =
|
||||||
|
{ Id = Giraffe.ShortGuid.fromGuid (row.uuid "id")
|
||||||
|
Name = row.string "group_name"
|
||||||
|
ChurchName = row.string "church_name"
|
||||||
|
TimeZoneId = TimeZoneId (row.string "time_zone_id")
|
||||||
|
}
|
||||||
|
|
||||||
/// Map a row to a Small Group list item
|
/// Map a row to a Small Group list item
|
||||||
let mapToSmallGroupItem (row : RowReader) =
|
let mapToSmallGroupItem (row : RowReader) =
|
||||||
Giraffe.ShortGuid.fromGuid (row.uuid "id"), $"""{row.string "church_name"} | {row.string "group_name"}"""
|
Giraffe.ShortGuid.fromGuid (row.uuid "id"), $"""{row.string "church_name"} | {row.string "group_name"}"""
|
||||||
|
@ -156,6 +180,14 @@ module Churches =
|
||||||
/// Functions to manipulate small group members
|
/// Functions to manipulate small group members
|
||||||
module Members =
|
module Members =
|
||||||
|
|
||||||
|
/// Count members for the given small group
|
||||||
|
let countByGroup (groupId : SmallGroupId) conn =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query "SELECT COUNT(id) AS mbr_count FROM pt.member WHERE small_group_id = @groupId"
|
||||||
|
|> Sql.parameters [ "@groupId", Sql.uuid groupId.Value ]
|
||||||
|
|> Sql.executeRowAsync (fun row -> row.int "mbr_count")
|
||||||
|
|
||||||
/// Delete a small group member by its ID
|
/// Delete a small group member by its ID
|
||||||
let deleteById (memberId : MemberId) conn = backgroundTask {
|
let deleteById (memberId : MemberId) conn = backgroundTask {
|
||||||
let! _ =
|
let! _ =
|
||||||
|
@ -167,6 +199,14 @@ module Members =
|
||||||
return ()
|
return ()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieve all members for a given small group
|
||||||
|
let forGroup (groupId : SmallGroupId) conn =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query "SELECT * FROM pt.member WHERE small_group_id = @groupId ORDER BY member_name"
|
||||||
|
|> Sql.parameters [ "@groupId", Sql.uuid groupId.Value ]
|
||||||
|
|> Sql.executeAsync mapToMember
|
||||||
|
|
||||||
/// Retrieve a small group member by its ID
|
/// Retrieve a small group member by its ID
|
||||||
let tryById (memberId : MemberId) conn = backgroundTask {
|
let tryById (memberId : MemberId) conn = backgroundTask {
|
||||||
let! mbr =
|
let! mbr =
|
||||||
|
@ -179,9 +219,38 @@ module Members =
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Options to retrieve a list of requests
|
||||||
|
type PrayerRequestOptions =
|
||||||
|
{ /// The small group for which requests should be retrieved
|
||||||
|
SmallGroup : SmallGroup
|
||||||
|
|
||||||
|
/// The clock instance to use for date/time manipulation
|
||||||
|
Clock : IClock
|
||||||
|
|
||||||
|
/// The date for which the list is being retrieved
|
||||||
|
ListDate : LocalDate option
|
||||||
|
|
||||||
|
/// Whether only active requests should be retrieved
|
||||||
|
ActiveOnly : bool
|
||||||
|
|
||||||
|
/// The page number, for paged lists
|
||||||
|
PageNumber : int
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Functions to manipulate prayer requests
|
/// Functions to manipulate prayer requests
|
||||||
module PrayerRequests =
|
module PrayerRequests =
|
||||||
|
|
||||||
|
/// Central place to append sort criteria for prayer request queries
|
||||||
|
let private orderBy sort =
|
||||||
|
match sort with
|
||||||
|
| SortByDate -> "DESC updated_date, DESC entered_date, requestor"
|
||||||
|
| SortByRequestor -> "requestor, DESC updated_date, DESC entered_date"
|
||||||
|
|
||||||
|
/// Paginate a prayer request query
|
||||||
|
let private paginate (pageNbr : int) pageSize =
|
||||||
|
if pageNbr > 0 then $"LIMIT {pageSize} OFFSET {(pageNbr - 1) * pageSize}" else ""
|
||||||
|
|
||||||
/// Count the number of prayer requests for a church
|
/// Count the number of prayer requests for a church
|
||||||
let countByChurch (churchId : ChurchId) conn =
|
let countByChurch (churchId : ChurchId) conn =
|
||||||
conn
|
conn
|
||||||
|
@ -200,8 +269,40 @@ module PrayerRequests =
|
||||||
|> Sql.query "SELECT COUNT(id) AS req_count FROM pt.prayer_request WHERE small_group_id = @groupId"
|
|> Sql.query "SELECT COUNT(id) AS req_count FROM pt.prayer_request WHERE small_group_id = @groupId"
|
||||||
|> Sql.parameters [ "@groupId", Sql.uuid groupId.Value ]
|
|> Sql.parameters [ "@groupId", Sql.uuid groupId.Value ]
|
||||||
|> Sql.executeRowAsync (fun row -> row.int "req_count")
|
|> Sql.executeRowAsync (fun row -> row.int "req_count")
|
||||||
|
|
||||||
|
/// Get all (or active) requests for a small group as of now or the specified date
|
||||||
|
let forGroup (opts : PrayerRequestOptions) conn =
|
||||||
|
let theDate = defaultArg opts.ListDate (SmallGroup.localDateNow opts.Clock opts.SmallGroup)
|
||||||
|
let where, parameters =
|
||||||
|
if opts.ActiveOnly then
|
||||||
|
let asOf = NpgsqlParameter (
|
||||||
|
"@asOf",
|
||||||
|
(theDate.AtStartOfDayInZone(SmallGroup.timeZone opts.SmallGroup)
|
||||||
|
- Duration.FromDays opts.SmallGroup.Preferences.DaysToExpire)
|
||||||
|
.ToInstant ())
|
||||||
|
""" AND ( updatedDate > @asOf
|
||||||
|
OR expiration = @manual
|
||||||
|
OR request_type = @longTerm
|
||||||
|
OR request_type = @expecting)
|
||||||
|
AND expiration <> @forced""",
|
||||||
|
[ "@asOf", Sql.parameter asOf
|
||||||
|
"@manual", Sql.string (Expiration.toCode Manual)
|
||||||
|
"@longTerm", Sql.string (PrayerRequestType.toCode LongTermRequest)
|
||||||
|
"@expecting", Sql.string (PrayerRequestType.toCode Expecting)
|
||||||
|
"@forced", Sql.string (Expiration.toCode Forced) ]
|
||||||
|
else "", []
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query $"""
|
||||||
|
SELECT *
|
||||||
|
FROM prayer_request
|
||||||
|
WHERE small_group_id = @groupId {where}
|
||||||
|
ORDER BY {orderBy opts.SmallGroup.Preferences.RequestSort}
|
||||||
|
{paginate opts.PageNumber opts.SmallGroup.Preferences.PageSize}"""
|
||||||
|
|> Sql.parameters (("@groupId", Sql.uuid opts.SmallGroup.Id.Value) :: parameters)
|
||||||
|
|> Sql.executeAsync mapToPrayerRequest
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// Functions to retrieve small group information
|
/// Functions to retrieve small group information
|
||||||
module SmallGroups =
|
module SmallGroups =
|
||||||
|
|
||||||
|
@ -227,6 +328,18 @@ module SmallGroups =
|
||||||
return ()
|
return ()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get information for all small groups
|
||||||
|
let infoForAll conn =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query """
|
||||||
|
SELECT sg.id, c.church_name, lp.time_zone_id
|
||||||
|
FROM pt.small_group sg
|
||||||
|
INNER JOIN pt.church c ON c.id = sg.church_id
|
||||||
|
INNER JOIN pt.list_preference lp ON lp.small_group_id = sg.id
|
||||||
|
ORDER BY sg.group_name"""
|
||||||
|
|> Sql.executeAsync mapToSmallGroupInfo
|
||||||
|
|
||||||
/// Get a list of small group IDs along with a description that includes the church name
|
/// Get a list of small group IDs along with a description that includes the church name
|
||||||
let listAll conn =
|
let listAll conn =
|
||||||
conn
|
conn
|
||||||
|
@ -251,6 +364,22 @@ module SmallGroups =
|
||||||
ORDER BY c.church_name, g.group_name"""
|
ORDER BY c.church_name, g.group_name"""
|
||||||
|> Sql.executeAsync mapToSmallGroupItem
|
|> Sql.executeAsync mapToSmallGroupItem
|
||||||
|
|
||||||
|
/// Log on for a small group (includes list preferences)
|
||||||
|
let logOn (groupId : SmallGroupId) password conn = backgroundTask {
|
||||||
|
let! group =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query """
|
||||||
|
SELECT sg.*, lp.*
|
||||||
|
FROM pt.small_group sg
|
||||||
|
INNER JOIN pt.list_preference lp ON lp.small_group_id = sg.id
|
||||||
|
WHERE sg.id = @id
|
||||||
|
AND lp.group_password = @password"""
|
||||||
|
|> Sql.parameters [ "@id", Sql.uuid groupId.Value; "@password", Sql.string password ]
|
||||||
|
|> Sql.executeAsync mapToSmallGroupWithPreferences
|
||||||
|
return List.tryHead group
|
||||||
|
}
|
||||||
|
|
||||||
/// Get a small group by its ID
|
/// Get a small group by its ID
|
||||||
let tryById (groupId : SmallGroupId) conn = backgroundTask {
|
let tryById (groupId : SmallGroupId) conn = backgroundTask {
|
||||||
let! group =
|
let! group =
|
||||||
|
@ -345,7 +474,7 @@ module Users =
|
||||||
|> Sql.executeAsync mapToUser
|
|> Sql.executeAsync mapToUser
|
||||||
|
|
||||||
/// Save a user's information
|
/// Save a user's information
|
||||||
let save user conn = backgroundTask {
|
let save (user : User) conn = backgroundTask {
|
||||||
let! _ =
|
let! _ =
|
||||||
conn
|
conn
|
||||||
|> Sql.existingConnection
|
|> Sql.existingConnection
|
||||||
|
@ -410,7 +539,7 @@ module Users =
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update a user's password hash
|
/// Update a user's password hash
|
||||||
let updatePassword user conn = backgroundTask {
|
let updatePassword (user : User) conn = backgroundTask {
|
||||||
let! _ =
|
let! _ =
|
||||||
conn
|
conn
|
||||||
|> Sql.existingConnection
|
|> Sql.existingConnection
|
||||||
|
|
|
@ -956,3 +956,20 @@ module PrayerRequest =
|
||||||
if isExpired asOf group req then false
|
if isExpired asOf group req then false
|
||||||
else asOf.PlusWeeks -group.Preferences.LongTermUpdateWeeks
|
else asOf.PlusWeeks -group.Preferences.LongTermUpdateWeeks
|
||||||
>= req.UpdatedDate.InZone(SmallGroup.timeZone group).Date
|
>= req.UpdatedDate.InZone(SmallGroup.timeZone group).Date
|
||||||
|
|
||||||
|
|
||||||
|
/// Information needed to display the small group maintenance page
|
||||||
|
[<NoComparison; NoEquality>]
|
||||||
|
type SmallGroupInfo =
|
||||||
|
{ /// The ID of the small group
|
||||||
|
Id : string
|
||||||
|
|
||||||
|
/// The name of the small group
|
||||||
|
Name : string
|
||||||
|
|
||||||
|
/// The name of the church to which the small group belongs
|
||||||
|
ChurchName : string
|
||||||
|
|
||||||
|
/// The ID of the time zone for the small group
|
||||||
|
TimeZoneId : TimeZoneId
|
||||||
|
}
|
||||||
|
|
|
@ -178,7 +178,7 @@ let logOn (groups : (string * string) list) grpId ctx viewInfo =
|
||||||
|
|
||||||
|
|
||||||
/// View for the small group maintenance page
|
/// View for the small group maintenance page
|
||||||
let maintain (groups : SmallGroup list) ctx viewInfo =
|
let maintain (groups : SmallGroupInfo list) ctx viewInfo =
|
||||||
let s = I18N.localizer.Force ()
|
let s = I18N.localizer.Force ()
|
||||||
let vi = AppViewInfo.withScopedStyles [ "#groupList { grid-template-columns: repeat(4, auto); }" ] viewInfo
|
let vi = AppViewInfo.withScopedStyles [ "#groupList { grid-template-columns: repeat(4, auto); }" ] viewInfo
|
||||||
let groupTable =
|
let groupTable =
|
||||||
|
@ -193,13 +193,12 @@ let maintain (groups : SmallGroup list) ctx viewInfo =
|
||||||
header [ _class "cell" ] [ locStr s["Time Zone"] ]
|
header [ _class "cell" ] [ locStr s["Time Zone"] ]
|
||||||
]
|
]
|
||||||
for group in groups do
|
for group in groups do
|
||||||
let grpId = shortGuid group.Id.Value
|
let delAction = $"/small-group/{group.Id}/delete"
|
||||||
let delAction = $"/small-group/{grpId}/delete"
|
|
||||||
let delPrompt = s["Are you sure you want to delete this {0}? This action cannot be undone.",
|
let delPrompt = s["Are you sure you want to delete this {0}? This action cannot be undone.",
|
||||||
$"""{s["Small Group"].Value.ToLower ()} ({group.Name})""" ].Value
|
$"""{s["Small Group"].Value.ToLower ()} ({group.Name})""" ].Value
|
||||||
div [ _class "row" ] [
|
div [ _class "row" ] [
|
||||||
div [ _class "cell actions" ] [
|
div [ _class "cell actions" ] [
|
||||||
a [ _href $"/small-group/{grpId}/edit"; _title s["Edit This Group"].Value ] [
|
a [ _href $"/small-group/{group.Id}/edit"; _title s["Edit This Group"].Value ] [
|
||||||
iconSized 18 "edit"
|
iconSized 18 "edit"
|
||||||
]
|
]
|
||||||
a [ _href delAction
|
a [ _href delAction
|
||||||
|
@ -210,8 +209,8 @@ let maintain (groups : SmallGroup list) ctx viewInfo =
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
div [ _class "cell" ] [ str group.Name ]
|
div [ _class "cell" ] [ str group.Name ]
|
||||||
div [ _class "cell" ] [ str group.Church.Name ]
|
div [ _class "cell" ] [ str group.ChurchName ]
|
||||||
div [ _class "cell" ] [ locStr (TimeZones.name group.Preferences.TimeZoneId s) ]
|
div [ _class "cell" ] [ locStr (TimeZones.name group.TimeZoneId s) ]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
[ div [ _class "pt-center-text" ] [
|
[ div [ _class "pt-center-text" ] [
|
||||||
|
|
|
@ -107,8 +107,9 @@ open Microsoft.AspNetCore.Authentication.Cookies
|
||||||
let logOnSubmit : HttpHandler = requireAccess [ AccessLevel.Public ] >=> validateCsrf >=> fun next ctx -> task {
|
let logOnSubmit : HttpHandler = requireAccess [ AccessLevel.Public ] >=> validateCsrf >=> fun next ctx -> task {
|
||||||
match! ctx.TryBindFormAsync<GroupLogOn> () with
|
match! ctx.TryBindFormAsync<GroupLogOn> () with
|
||||||
| Ok model ->
|
| Ok model ->
|
||||||
let s = Views.I18N.localizer.Force ()
|
let s = Views.I18N.localizer.Force ()
|
||||||
match! ctx.Db.TryGroupLogOnByPassword (idFromShort SmallGroupId model.SmallGroupId) model.Password with
|
let! conn = ctx.Conn
|
||||||
|
match! SmallGroups.logOn (idFromShort SmallGroupId model.SmallGroupId) model.Password conn with
|
||||||
| Some group ->
|
| Some group ->
|
||||||
ctx.Session.CurrentGroup <- Some group
|
ctx.Session.CurrentGroup <- Some group
|
||||||
let identity = ClaimsIdentity (
|
let identity = ClaimsIdentity (
|
||||||
|
@ -129,7 +130,8 @@ let logOnSubmit : HttpHandler = requireAccess [ AccessLevel.Public ] >=> validat
|
||||||
|
|
||||||
/// GET /small-groups
|
/// GET /small-groups
|
||||||
let maintain : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> task {
|
let maintain : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> task {
|
||||||
let! groups = ctx.Db.AllGroups ()
|
let! conn = ctx.Conn
|
||||||
|
let! groups = SmallGroups.infoForAll conn
|
||||||
return!
|
return!
|
||||||
viewInfo ctx
|
viewInfo ctx
|
||||||
|> Views.SmallGroup.maintain groups ctx
|
|> Views.SmallGroup.maintain groups ctx
|
||||||
|
@ -140,7 +142,8 @@ let maintain : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> task {
|
||||||
let members : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task {
|
let members : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task {
|
||||||
let group = ctx.Session.CurrentGroup.Value
|
let group = ctx.Session.CurrentGroup.Value
|
||||||
let s = Views.I18N.localizer.Force ()
|
let s = Views.I18N.localizer.Force ()
|
||||||
let! members = ctx.Db.AllMembersForSmallGroup group.Id
|
let! conn = ctx.Conn
|
||||||
|
let! members = Members.forGroup group.Id conn
|
||||||
let types = ReferenceList.emailTypeList group.Preferences.DefaultEmailType s |> Map.ofSeq
|
let types = ReferenceList.emailTypeList group.Preferences.DefaultEmailType s |> Map.ofSeq
|
||||||
return!
|
return!
|
||||||
{ viewInfo ctx with HelpLink = Some Help.maintainGroupMembers }
|
{ viewInfo ctx with HelpLink = Some Help.maintainGroupMembers }
|
||||||
|
@ -152,10 +155,16 @@ let members : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task {
|
||||||
let overview : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task {
|
let overview : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task {
|
||||||
let group = ctx.Session.CurrentGroup.Value
|
let group = ctx.Session.CurrentGroup.Value
|
||||||
let! conn = ctx.Conn
|
let! conn = ctx.Conn
|
||||||
let! reqs = ctx.Db.AllRequestsForSmallGroup group ctx.Clock None true 0
|
let! reqs = PrayerRequests.forGroup
|
||||||
let! reqCount = ctx.Db.CountRequestsBySmallGroup group.Id
|
{ SmallGroup = group
|
||||||
let! mbrCount = ctx.Db.CountMembersForSmallGroup group.Id
|
Clock = ctx.Clock
|
||||||
let! admins = Users.listByGroupId group.Id conn
|
ListDate = None
|
||||||
|
ActiveOnly = true
|
||||||
|
PageNumber = 0
|
||||||
|
} conn
|
||||||
|
let! reqCount = PrayerRequests.countByGroup group.Id conn
|
||||||
|
let! mbrCount = Members.countByGroup group.Id conn
|
||||||
|
let! admins = Users.listByGroupId group.Id conn
|
||||||
let model =
|
let model =
|
||||||
{ TotalActiveReqs = List.length reqs
|
{ TotalActiveReqs = List.length reqs
|
||||||
AllReqs = reqCount
|
AllReqs = reqCount
|
||||||
|
@ -177,6 +186,7 @@ let overview : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task {
|
||||||
|
|
||||||
/// GET /small-group/preferences
|
/// GET /small-group/preferences
|
||||||
let preferences : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task {
|
let preferences : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task {
|
||||||
|
// TODO: stopped here
|
||||||
let group = ctx.Session.CurrentGroup.Value
|
let group = ctx.Session.CurrentGroup.Value
|
||||||
let! tzs = ctx.Db.AllTimeZones ()
|
let! tzs = ctx.Db.AllTimeZones ()
|
||||||
return!
|
return!
|
||||||
|
|
Loading…
Reference in New Issue
Block a user