WIP on SQL migration
- Add admins to small group overview page
This commit is contained in:
parent
37dcf41c98
commit
e621cd6a1f
|
@ -12,7 +12,7 @@ module private Helpers =
|
||||||
/// Map a row to a Church instance
|
/// Map a row to a Church instance
|
||||||
let mapToChurch (row : RowReader) =
|
let mapToChurch (row : RowReader) =
|
||||||
{ Id = ChurchId (row.uuid "id")
|
{ Id = ChurchId (row.uuid "id")
|
||||||
Name = row.string "name"
|
Name = row.string "church_name"
|
||||||
City = row.string "city"
|
City = row.string "city"
|
||||||
State = row.string "state"
|
State = row.string "state"
|
||||||
HasVpsInterface = row.bool "has_vps_interface"
|
HasVpsInterface = row.bool "has_vps_interface"
|
||||||
|
@ -32,16 +32,26 @@ module private Helpers =
|
||||||
LineColor = row.string "line_color"
|
LineColor = row.string "line_color"
|
||||||
HeadingFontSize = row.int "heading_font_size"
|
HeadingFontSize = row.int "heading_font_size"
|
||||||
TextFontSize = row.int "text_font_size"
|
TextFontSize = row.int "text_font_size"
|
||||||
RequestSort = RequestSort.fromCode (row.string "request_sort")
|
|
||||||
GroupPassword = row.string "group_password"
|
GroupPassword = row.string "group_password"
|
||||||
DefaultEmailType = EmailFormat.fromCode (row.string "default_email_type")
|
|
||||||
IsPublic = row.bool "is_public"
|
IsPublic = row.bool "is_public"
|
||||||
TimeZoneId = TimeZoneId (row.string "time_zone_id")
|
|
||||||
PageSize = row.int "page_size"
|
PageSize = row.int "page_size"
|
||||||
|
TimeZoneId = TimeZoneId (row.string "time_zone_id")
|
||||||
|
RequestSort = RequestSort.fromCode (row.string "request_sort")
|
||||||
|
DefaultEmailType = EmailFormat.fromCode (row.string "default_email_type")
|
||||||
AsOfDateDisplay = AsOfDateDisplay.fromCode (row.string "as_of_date_display")
|
AsOfDateDisplay = AsOfDateDisplay.fromCode (row.string "as_of_date_display")
|
||||||
TimeZone = TimeZone.empty
|
TimeZone = TimeZone.empty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Map a row to a Member instance
|
||||||
|
let mapToMember (row : RowReader) =
|
||||||
|
{ Id = MemberId (row.uuid "id")
|
||||||
|
SmallGroupId = SmallGroupId (row.uuid "small_group_id")
|
||||||
|
Name = row.string "member_name"
|
||||||
|
Email = row.string "email"
|
||||||
|
Format = row.stringOrNone "email_format" |> Option.map EmailFormat.fromCode
|
||||||
|
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")
|
||||||
|
@ -54,6 +64,10 @@ module private Helpers =
|
||||||
Users = ResizeArray ()
|
Users = ResizeArray ()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Map a row to a Small Group list item
|
||||||
|
let mapToSmallGroupItem (row : RowReader) =
|
||||||
|
Giraffe.ShortGuid.fromGuid (row.uuid "id"), $"""{row.string "church_name"} | {row.string "group_name"}"""
|
||||||
|
|
||||||
/// Map a row to a Small Group instance with populated list preferences
|
/// Map a row to a Small Group instance with populated list preferences
|
||||||
let mapToSmallGroupWithPreferences (row : RowReader) =
|
let mapToSmallGroupWithPreferences (row : RowReader) =
|
||||||
{ mapToSmallGroup row with
|
{ mapToSmallGroup row with
|
||||||
|
@ -74,7 +88,60 @@ module private Helpers =
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Functions to manipulate churches
|
||||||
module Churches =
|
module Churches =
|
||||||
|
|
||||||
|
/// Get a list of all churches
|
||||||
|
let all conn =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query "SELECT * FROM pt.church ORDER BY church_name"
|
||||||
|
|> Sql.executeAsync mapToChurch
|
||||||
|
|
||||||
|
/// Delete a church by its ID
|
||||||
|
let deleteById (churchId : ChurchId) conn = backgroundTask {
|
||||||
|
let idParam = [ [ "@churchId", Sql.uuid churchId.Value ] ]
|
||||||
|
let where = "WHERE small_group_id IN (SELECT id FROM pt.small_group WHERE church_id = @churchId)"
|
||||||
|
let! _ =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.executeTransactionAsync
|
||||||
|
[ $"DELETE FROM pt.prayer_request {where}", idParam
|
||||||
|
$"DELETE FROM pt.user_small_group {where}", idParam
|
||||||
|
$"DELETE FROM pt.list_preference {where}", idParam
|
||||||
|
"DELETE FROM pt.small_group WHERE church_id = @churchId", idParam
|
||||||
|
"DELETE FROM pt.church WHERE id = @churchId", idParam ]
|
||||||
|
return ()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save a church's information
|
||||||
|
let save (church : Church) conn = backgroundTask {
|
||||||
|
let! _ =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query """
|
||||||
|
INSERT INTO pt.church (
|
||||||
|
id, church_name, city, state, has_vps_interface, interface_address
|
||||||
|
) VALUES (
|
||||||
|
@id, @name, @city, @state, @hasVpsInterface, @interfaceAddress
|
||||||
|
) ON CONFLICT (id) DO UPDATE
|
||||||
|
SET church_name = EXCLUDED.church_name,
|
||||||
|
city = EXCLUDED.city,
|
||||||
|
state = EXCLUDED.state,
|
||||||
|
has_vps_interface = EXCLUDED.has_vps_interface,
|
||||||
|
interface_address = EXCLUDED.interface_address"""
|
||||||
|
|> Sql.parameters
|
||||||
|
[ "@id", Sql.uuid church.Id.Value
|
||||||
|
"@name", Sql.string church.Name
|
||||||
|
"@city", Sql.string church.City
|
||||||
|
"@state", Sql.string church.State
|
||||||
|
"@hasVpsInterface", Sql.bool church.HasVpsInterface
|
||||||
|
"@interfaceAddress", Sql.stringOrNone church.InterfaceAddress ]
|
||||||
|
|> Sql.executeNonQueryAsync
|
||||||
|
return ()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find a church by its ID
|
||||||
let tryById (churchId : ChurchId) conn = backgroundTask {
|
let tryById (churchId : ChurchId) conn = backgroundTask {
|
||||||
let! church =
|
let! church =
|
||||||
conn
|
conn
|
||||||
|
@ -86,9 +153,80 @@ module Churches =
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Functions to manipulate small group members
|
||||||
|
module Members =
|
||||||
|
|
||||||
|
/// Delete a small group member by its ID
|
||||||
|
let deleteById (memberId : MemberId) conn = backgroundTask {
|
||||||
|
let! _ =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query "DELETE FROM pt.member WHERE id = @id"
|
||||||
|
|> Sql.parameters [ "@id", Sql.uuid memberId.Value ]
|
||||||
|
|> Sql.executeNonQueryAsync
|
||||||
|
return ()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve a small group member by its ID
|
||||||
|
let tryById (memberId : MemberId) conn = backgroundTask {
|
||||||
|
let! mbr =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query "SELECT * FROM pt.member WHERE id = @id"
|
||||||
|
|> Sql.parameters [ "@id", Sql.uuid memberId.Value ]
|
||||||
|
|> Sql.executeAsync mapToMember
|
||||||
|
return List.tryHead mbr
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Functions to manipulate prayer requests
|
||||||
|
module PrayerRequests =
|
||||||
|
|
||||||
|
/// Count the number of prayer requests for a church
|
||||||
|
let countByChurch (churchId : ChurchId) conn =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query """
|
||||||
|
SELECT COUNT(id) AS req_count
|
||||||
|
FROM pt.prayer_request
|
||||||
|
WHERE small_group_id IN (SELECT id FROM pt.small_group WHERE church_id = @churchId)"""
|
||||||
|
|> Sql.parameters [ "@churchId", Sql.uuid churchId.Value ]
|
||||||
|
|> Sql.executeRowAsync (fun row -> row.int "req_count")
|
||||||
|
|
||||||
|
/// Count the number of prayer requests for a small group
|
||||||
|
let countByGroup (groupId : SmallGroupId) conn =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> 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.executeRowAsync (fun row -> row.int "req_count")
|
||||||
|
|
||||||
|
|
||||||
/// Functions to retrieve small group information
|
/// Functions to retrieve small group information
|
||||||
module SmallGroups =
|
module SmallGroups =
|
||||||
|
|
||||||
|
/// Count the number of small groups for a church
|
||||||
|
let countByChurch (churchId : ChurchId) conn =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query "SELECT COUNT(id) AS group_count FROM pt.small_group WHERE church_id = @churchId"
|
||||||
|
|> Sql.parameters [ "@churchId", Sql.uuid churchId.Value ]
|
||||||
|
|> Sql.executeRowAsync (fun row -> row.int "group_count")
|
||||||
|
|
||||||
|
/// Delete a small group by its ID
|
||||||
|
let deleteById (groupId : SmallGroupId) conn = backgroundTask {
|
||||||
|
let idParam = [ [ "@groupId", Sql.uuid groupId.Value ] ]
|
||||||
|
let! _ =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.executeTransactionAsync
|
||||||
|
[ "DELETE FROM pt.prayer_request WHERE small_group_id = @groupId", idParam
|
||||||
|
"DELETE FROM pt.user_small_group WHERE small_group_id = @groupId", idParam
|
||||||
|
"DELETE FROM pt.list_preference WHERE small_group_id = @groupId", idParam
|
||||||
|
"DELETE FROM pt.small_group WHERE id = @groupId", idParam ]
|
||||||
|
return ()
|
||||||
|
}
|
||||||
|
|
||||||
/// 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
|
||||||
|
@ -98,9 +236,33 @@ module SmallGroups =
|
||||||
FROM pt.small_group g
|
FROM pt.small_group g
|
||||||
INNER JOIN pt.church c ON c.id = g.church_id
|
INNER JOIN pt.church c ON c.id = g.church_id
|
||||||
ORDER BY c.church_name, g.group_name"""
|
ORDER BY c.church_name, g.group_name"""
|
||||||
|> Sql.executeAsync (fun row ->
|
|> Sql.executeAsync mapToSmallGroupItem
|
||||||
Giraffe.ShortGuid.fromGuid (row.uuid "id"), $"""{row.string "church_name"} | {row.string "group_name"}""")
|
|
||||||
|
|
||||||
|
/// Get a list of small group IDs and descriptions for groups with a group password
|
||||||
|
let listProtected conn =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query """
|
||||||
|
SELECT g.group_name, g.id, c.church_name
|
||||||
|
FROM pt.small_group g
|
||||||
|
INNER JOIN pt.church c ON c.id = g.church_id
|
||||||
|
INNER JOIN pt.list_preference lp ON lp.small_group_id = g.id
|
||||||
|
WHERE COALESCE(lp.group_password, '') <> ''
|
||||||
|
ORDER BY c.church_name, g.group_name"""
|
||||||
|
|> Sql.executeAsync mapToSmallGroupItem
|
||||||
|
|
||||||
|
/// Get a small group by its ID
|
||||||
|
let tryById (groupId : SmallGroupId) conn = backgroundTask {
|
||||||
|
let! group =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query "SELECT * FROM pt.small_group WHERE id = @id"
|
||||||
|
|> Sql.parameters [ "@id", Sql.uuid groupId.Value ]
|
||||||
|
|> Sql.executeAsync mapToSmallGroup
|
||||||
|
return List.tryHead group
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a small group by its ID with its list preferences populated
|
||||||
let tryByIdWithPreferences (groupId : SmallGroupId) conn = backgroundTask {
|
let tryByIdWithPreferences (groupId : SmallGroupId) conn = backgroundTask {
|
||||||
let! group =
|
let! group =
|
||||||
conn
|
conn
|
||||||
|
@ -108,7 +270,7 @@ module SmallGroups =
|
||||||
|> Sql.query """
|
|> Sql.query """
|
||||||
SELECT sg.*, lp.*
|
SELECT sg.*, lp.*
|
||||||
FROM pt.small_group sg
|
FROM pt.small_group sg
|
||||||
INNER JOIN list_preference lp ON lp.small_group_id = sg.id
|
INNER JOIN pt.list_preference lp ON lp.small_group_id = sg.id
|
||||||
WHERE sg.id = @id"""
|
WHERE sg.id = @id"""
|
||||||
|> Sql.parameters [ "@id", Sql.uuid groupId.Value ]
|
|> Sql.parameters [ "@id", Sql.uuid groupId.Value ]
|
||||||
|> Sql.executeAsync mapToSmallGroupWithPreferences
|
|> Sql.executeAsync mapToSmallGroupWithPreferences
|
||||||
|
@ -126,6 +288,30 @@ module Users =
|
||||||
|> Sql.query "SELECT * FROM pt.pt_user ORDER BY last_name, first_name"
|
|> Sql.query "SELECT * FROM pt.pt_user ORDER BY last_name, first_name"
|
||||||
|> Sql.executeAsync mapToUser
|
|> Sql.executeAsync mapToUser
|
||||||
|
|
||||||
|
/// Count the number of users for a church
|
||||||
|
let countByChurch (churchId : ChurchId) conn =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query """
|
||||||
|
SELECT COUNT(u.id) AS user_count
|
||||||
|
FROM pt.pt_user u
|
||||||
|
WHERE EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pt.user_small_group usg
|
||||||
|
INNER JOIN pt.small_group sg ON sg.id = usg.small_group_id
|
||||||
|
WHERE usg.user_id = u.id
|
||||||
|
AND sg.church_id = @churchId)"""
|
||||||
|
|> Sql.parameters [ "@churchId", Sql.uuid churchId.Value ]
|
||||||
|
|> Sql.executeRowAsync (fun row -> row.int "user_count")
|
||||||
|
|
||||||
|
/// Count the number of users for a small group
|
||||||
|
let countByGroup (groupId : SmallGroupId) conn =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query "SELECT COUNT(user_id) AS user_count FROM pt.user_small_group WHERE small_group_id = @groupId"
|
||||||
|
|> Sql.parameters [ "@groupId", Sql.uuid groupId.Value ]
|
||||||
|
|> Sql.executeRowAsync (fun row -> row.int "user_count")
|
||||||
|
|
||||||
/// Delete a user by its database ID
|
/// Delete a user by its database ID
|
||||||
let deleteById (userId : UserId) conn = backgroundTask {
|
let deleteById (userId : UserId) conn = backgroundTask {
|
||||||
let! _ =
|
let! _ =
|
||||||
|
@ -137,6 +323,27 @@ module Users =
|
||||||
return ()
|
return ()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the IDs of the small groups for which the given user is authorized
|
||||||
|
let groupIdsByUserId (userId : UserId) conn =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query "SELECT small_group_id FROM pt.user_small_group WHERE user_id = @id"
|
||||||
|
|> Sql.parameters [ "@id", Sql.uuid userId.Value ]
|
||||||
|
|> Sql.executeAsync (fun row -> SmallGroupId (row.uuid "small_group_id"))
|
||||||
|
|
||||||
|
/// Get a list of users authorized to administer the given small group
|
||||||
|
let listByGroupId (groupId : SmallGroupId) conn =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query """
|
||||||
|
SELECT u.*
|
||||||
|
FROM pt.pt_user u
|
||||||
|
INNER JOIN pt.user_small_group usg ON usg.user_id = u.id
|
||||||
|
WHERE usg.small_group_id = @groupId
|
||||||
|
ORDER BY u.last_name, u.first_name"""
|
||||||
|
|> Sql.parameters [ "@groupId", Sql.uuid groupId.Value ]
|
||||||
|
|> Sql.executeAsync mapToUser
|
||||||
|
|
||||||
/// Save a user's information
|
/// Save a user's information
|
||||||
let save user conn = backgroundTask {
|
let save user conn = backgroundTask {
|
||||||
let! _ =
|
let! _ =
|
||||||
|
@ -212,3 +419,27 @@ module Users =
|
||||||
|> Sql.executeNonQueryAsync
|
|> Sql.executeNonQueryAsync
|
||||||
return ()
|
return ()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update a user's authorized small groups
|
||||||
|
let updateSmallGroups (userId : UserId) groupIds conn = backgroundTask {
|
||||||
|
let! existingGroupIds = groupIdsByUserId userId conn
|
||||||
|
let toAdd =
|
||||||
|
groupIds |> List.filter (fun it -> existingGroupIds |> List.exists (fun grpId -> grpId = it) |> not)
|
||||||
|
let toDelete =
|
||||||
|
existingGroupIds |> List.filter (fun it -> groupIds |> List.exists (fun grpId -> grpId = it) |> not)
|
||||||
|
let queries = seq {
|
||||||
|
if not (List.isEmpty toAdd) then
|
||||||
|
"INSERT INTO pt.user_small_group VALUES (@userId, @smallGroupId)",
|
||||||
|
toAdd |> List.map (fun it -> [ "@userId", Sql.uuid userId.Value; "@smallGroupId", Sql.uuid it.Value ])
|
||||||
|
if not (List.isEmpty toDelete) then
|
||||||
|
"DELETE FROM pt.user_small_group WHERE user_id = @userId AND small_group_id = @smallGroupId",
|
||||||
|
toDelete
|
||||||
|
|> List.map (fun it -> [ "@userId", Sql.uuid userId.Value; "@smallGroupId", Sql.uuid it.Value ])
|
||||||
|
}
|
||||||
|
if not (Seq.isEmpty queries) then
|
||||||
|
let! _ =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.executeTransactionAsync (List.ofSeq queries)
|
||||||
|
()
|
||||||
|
}
|
||||||
|
|
|
@ -828,4 +828,7 @@
|
||||||
<data name="Last Seen" xml:space="preserve">
|
<data name="Last Seen" xml:space="preserve">
|
||||||
<value>Ultima vez Visto</value>
|
<value>Ultima vez Visto</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Administrators" xml:space="preserve">
|
||||||
|
<value>Administradores</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
|
@ -141,7 +141,7 @@ let editMember (model : EditMember) (types : (string * LocalizedString) seq) ctx
|
||||||
|
|
||||||
|
|
||||||
/// View for the small group log on page
|
/// View for the small group log on page
|
||||||
let logOn (groups : SmallGroup list) grpId ctx viewInfo =
|
let logOn (groups : (string * string) list) grpId ctx viewInfo =
|
||||||
let s = I18N.localizer.Force ()
|
let s = I18N.localizer.Force ()
|
||||||
let model = { SmallGroupId = emptyGuid; Password = ""; RememberMe = None }
|
let model = { SmallGroupId = emptyGuid; Password = ""; RememberMe = None }
|
||||||
let vi = AppViewInfo.withOnLoadScript "PT.smallGroup.logOn.onPageLoad" viewInfo
|
let vi = AppViewInfo.withOnLoadScript "PT.smallGroup.logOn.onPageLoad" viewInfo
|
||||||
|
@ -154,9 +154,7 @@ let logOn (groups : SmallGroup list) grpId ctx viewInfo =
|
||||||
if groups.Length = 0 then "", s["There are no classes with passwords defined"].Value
|
if groups.Length = 0 then "", s["There are no classes with passwords defined"].Value
|
||||||
else
|
else
|
||||||
"", selectDefault s["Select Group"].Value
|
"", selectDefault s["Select Group"].Value
|
||||||
yield!
|
yield! groups
|
||||||
groups
|
|
||||||
|> List.map (fun grp -> shortGuid grp.Id.Value, $"{grp.Church.Name} | {grp.Name}")
|
|
||||||
}
|
}
|
||||||
|> selectList (nameof model.SmallGroupId) grpId [ _required ]
|
|> selectList (nameof model.SmallGroupId) grpId [ _required ]
|
||||||
]
|
]
|
||||||
|
@ -336,6 +334,13 @@ let overview model viewInfo =
|
||||||
strong [] [ str (model.TotalMembers.ToString "N0"); space; locStr s["Members"] ]
|
strong [] [ str (model.TotalMembers.ToString "N0"); space; locStr s["Members"] ]
|
||||||
hr []
|
hr []
|
||||||
a [ _href "/small-group/members" ] [ icon "email"; linkSpacer; locStr s["Maintain Group Members"] ]
|
a [ _href "/small-group/members" ] [ icon "email"; linkSpacer; locStr s["Maintain Group Members"] ]
|
||||||
|
hr []
|
||||||
|
strong [] [ str ((List.length model.Admins).ToString "N0"); space; locStr s["Administrators"] ]
|
||||||
|
for admin in model.Admins do
|
||||||
|
hr []
|
||||||
|
str admin.Name
|
||||||
|
br []
|
||||||
|
small [] [ a [ _href $"mailto:{admin.Email}" ] [ str admin.Email ] ]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
|
@ -688,6 +688,9 @@ type Overview =
|
||||||
|
|
||||||
/// A count of all members
|
/// A count of all members
|
||||||
TotalMembers : int
|
TotalMembers : int
|
||||||
|
|
||||||
|
/// The users authorized to administer this group
|
||||||
|
Admins : User list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,27 +1,28 @@
|
||||||
module PrayerTracker.Handlers.Church
|
module PrayerTracker.Handlers.Church
|
||||||
|
|
||||||
|
open System.Threading.Tasks
|
||||||
open Giraffe
|
open Giraffe
|
||||||
open PrayerTracker
|
open PrayerTracker
|
||||||
|
open PrayerTracker.Data
|
||||||
open PrayerTracker.Entities
|
open PrayerTracker.Entities
|
||||||
open PrayerTracker.ViewModels
|
open PrayerTracker.ViewModels
|
||||||
|
|
||||||
/// Find statistics for the given church
|
/// Find statistics for the given church
|
||||||
let private findStats (db : AppDbContext) churchId = task {
|
let private findStats churchId conn = task {
|
||||||
let! grps = db.CountGroupsByChurch churchId
|
let! groups = SmallGroups.countByChurch churchId conn
|
||||||
let! reqs = db.CountRequestsByChurch churchId
|
let! requests = PrayerRequests.countByChurch churchId conn
|
||||||
let! usrs = db.CountUsersByChurch churchId
|
let! users = Users.countByChurch churchId conn
|
||||||
return shortGuid churchId.Value, { SmallGroups = grps; PrayerRequests = reqs; Users = usrs }
|
return shortGuid churchId.Value, { SmallGroups = groups; PrayerRequests = requests; Users = users }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// POST /church/[church-id]/delete
|
/// POST /church/[church-id]/delete
|
||||||
let delete chId : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next ctx -> task {
|
let delete chId : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next ctx -> task {
|
||||||
let churchId = ChurchId chId
|
let churchId = ChurchId chId
|
||||||
use! conn = ctx.Conn
|
use! conn = ctx.Conn
|
||||||
match! Data.Churches.tryById churchId conn with
|
match! Churches.tryById churchId conn with
|
||||||
| Some church ->
|
| Some church ->
|
||||||
let! _, stats = findStats ctx.Db churchId
|
let! _, stats = findStats churchId conn
|
||||||
ctx.Db.RemoveEntry church
|
do! Churches.deleteById churchId conn
|
||||||
let! _ = ctx.Db.SaveChangesAsync ()
|
|
||||||
let s = Views.I18N.localizer.Force ()
|
let s = Views.I18N.localizer.Force ()
|
||||||
addInfo ctx
|
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)",
|
s["The church {0} and its {1} small groups (with {2} prayer request(s)) were deleted successfully; revoked access from {3} user(s)",
|
||||||
|
@ -41,7 +42,7 @@ let edit churchId : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> ta
|
||||||
|> renderHtml next ctx
|
|> renderHtml next ctx
|
||||||
else
|
else
|
||||||
use! conn = ctx.Conn
|
use! conn = ctx.Conn
|
||||||
match! Data.Churches.tryById (ChurchId churchId) conn with
|
match! Churches.tryById (ChurchId churchId) conn with
|
||||||
| Some church ->
|
| Some church ->
|
||||||
return!
|
return!
|
||||||
viewInfo ctx
|
viewInfo ctx
|
||||||
|
@ -52,17 +53,15 @@ let edit churchId : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> ta
|
||||||
|
|
||||||
/// GET /churches
|
/// GET /churches
|
||||||
let maintain : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> task {
|
let maintain : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> task {
|
||||||
let await = Async.AwaitTask >> Async.RunSynchronously
|
let! conn = ctx.Conn
|
||||||
let! churches = ctx.Db.AllChurches ()
|
let! churches = Churches.all conn
|
||||||
let stats = churches |> List.map (fun c -> await (findStats ctx.Db c.Id))
|
let stats = churches |> List.map (fun c -> findStats c.Id conn |> Async.AwaitTask |> Async.RunSynchronously)
|
||||||
return!
|
return!
|
||||||
viewInfo ctx
|
viewInfo ctx
|
||||||
|> Views.Church.maintain churches (stats |> Map.ofList) ctx
|
|> Views.Church.maintain churches (stats |> Map.ofList) ctx
|
||||||
|> renderHtml next ctx
|
|> renderHtml next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
open System.Threading.Tasks
|
|
||||||
|
|
||||||
/// POST /church/save
|
/// POST /church/save
|
||||||
let save : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next ctx -> task {
|
let save : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next ctx -> task {
|
||||||
match! ctx.TryBindFormAsync<EditChurch> () with
|
match! ctx.TryBindFormAsync<EditChurch> () with
|
||||||
|
@ -70,12 +69,10 @@ let save : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next c
|
||||||
let! conn = ctx.Conn
|
let! conn = ctx.Conn
|
||||||
let! church =
|
let! church =
|
||||||
if model.IsNew then Task.FromResult (Some { Church.empty with Id = (Guid.NewGuid >> ChurchId) () })
|
if model.IsNew then Task.FromResult (Some { Church.empty with Id = (Guid.NewGuid >> ChurchId) () })
|
||||||
else Data.Churches.tryById (idFromShort ChurchId model.ChurchId) conn
|
else Churches.tryById (idFromShort ChurchId model.ChurchId) conn
|
||||||
match church with
|
match church with
|
||||||
| Some ch ->
|
| Some ch ->
|
||||||
model.PopulateChurch ch
|
do! Churches.save (model.PopulateChurch ch) conn
|
||||||
|> (if model.IsNew then ctx.Db.AddEntry else ctx.Db.UpdateEntry)
|
|
||||||
let! _ = ctx.Db.SaveChangesAsync ()
|
|
||||||
let s = Views.I18N.localizer.Force ()
|
let s = Views.I18N.localizer.Force ()
|
||||||
let act = s[if model.IsNew then "Added" else "Updated"].Value.ToLower ()
|
let act = s[if model.IsNew then "Added" else "Updated"].Value.ToLower ()
|
||||||
addInfo ctx s["Successfully {0} church “{1}”", act, model.Name]
|
addInfo ctx s["Successfully {0} church “{1}”", act, model.Name]
|
||||||
|
|
|
@ -6,6 +6,7 @@ open Microsoft.FSharpLu
|
||||||
open Newtonsoft.Json
|
open Newtonsoft.Json
|
||||||
open NodaTime
|
open NodaTime
|
||||||
open NodaTime.Serialization.JsonNet
|
open NodaTime.Serialization.JsonNet
|
||||||
|
open PrayerTracker.Data
|
||||||
open PrayerTracker.Entities
|
open PrayerTracker.Entities
|
||||||
open PrayerTracker.ViewModels
|
open PrayerTracker.ViewModels
|
||||||
|
|
||||||
|
@ -107,7 +108,8 @@ type HttpContext with
|
||||||
| None ->
|
| None ->
|
||||||
match this.User.SmallGroupId with
|
match this.User.SmallGroupId with
|
||||||
| Some groupId ->
|
| Some groupId ->
|
||||||
match! this.Db.TryGroupById groupId with
|
let! conn = this.Conn
|
||||||
|
match! SmallGroups.tryByIdWithPreferences groupId conn with
|
||||||
| Some group ->
|
| Some group ->
|
||||||
this.Session.CurrentGroup <- Some group
|
this.Session.CurrentGroup <- Some group
|
||||||
return Some group
|
return Some group
|
||||||
|
@ -122,11 +124,11 @@ type HttpContext with
|
||||||
| None ->
|
| None ->
|
||||||
match this.User.UserId with
|
match this.User.UserId with
|
||||||
| Some userId ->
|
| Some userId ->
|
||||||
match! this.Db.TryUserById userId with
|
let! conn = this.Conn
|
||||||
|
match! Users.tryById userId conn with
|
||||||
| Some user ->
|
| Some user ->
|
||||||
// Set last seen for user
|
// Set last seen for user
|
||||||
this.Db.UpdateEntry { user with LastSeen = Some this.Now }
|
do! Users.updateLastSeen userId this.Now conn
|
||||||
let! _ = this.Db.SaveChangesAsync ()
|
|
||||||
this.Session.CurrentUser <- Some user
|
this.Session.CurrentUser <- Some user
|
||||||
return Some user
|
return Some user
|
||||||
| None -> return None
|
| None -> return None
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
open System
|
open System
|
||||||
open Giraffe
|
open Giraffe
|
||||||
open PrayerTracker
|
open PrayerTracker
|
||||||
|
open PrayerTracker.Data
|
||||||
open PrayerTracker.Entities
|
open PrayerTracker.Entities
|
||||||
open PrayerTracker.ViewModels
|
open PrayerTracker.ViewModels
|
||||||
|
|
||||||
|
@ -16,12 +17,12 @@ let announcement : HttpHandler = requireAccess [ User ] >=> fun next ctx ->
|
||||||
let delete grpId : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next ctx -> task {
|
let delete grpId : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next ctx -> task {
|
||||||
let s = Views.I18N.localizer.Force ()
|
let s = Views.I18N.localizer.Force ()
|
||||||
let groupId = SmallGroupId grpId
|
let groupId = SmallGroupId grpId
|
||||||
match! ctx.Db.TryGroupById groupId with
|
let! conn = ctx.Conn
|
||||||
|
match! SmallGroups.tryById groupId conn with
|
||||||
| Some grp ->
|
| Some grp ->
|
||||||
let! reqs = ctx.Db.CountRequestsBySmallGroup groupId
|
let! reqs = PrayerRequests.countByGroup groupId conn
|
||||||
let! users = ctx.Db.CountUsersBySmallGroup groupId
|
let! users = Users.countByGroup groupId conn
|
||||||
ctx.Db.RemoveEntry grp
|
do! SmallGroups.deleteById groupId conn
|
||||||
let! _ = ctx.Db.SaveChangesAsync ()
|
|
||||||
addInfo ctx
|
addInfo ctx
|
||||||
s["The group {0} and its {1} prayer request(s) were deleted successfully; revoked access from {2} user(s)",
|
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]
|
||||||
|
@ -34,10 +35,10 @@ let deleteMember mbrId : HttpHandler = requireAccess [ User ] >=> validateCsrf >
|
||||||
let s = Views.I18N.localizer.Force ()
|
let s = Views.I18N.localizer.Force ()
|
||||||
let group = ctx.Session.CurrentGroup.Value
|
let group = ctx.Session.CurrentGroup.Value
|
||||||
let memberId = MemberId mbrId
|
let memberId = MemberId mbrId
|
||||||
match! ctx.Db.TryMemberById memberId with
|
let! conn = ctx.Conn
|
||||||
|
match! Members.tryById memberId conn with
|
||||||
| Some mbr when mbr.SmallGroupId = group.Id ->
|
| Some mbr when mbr.SmallGroupId = group.Id ->
|
||||||
ctx.Db.RemoveEntry mbr
|
do! Members.deleteById memberId conn
|
||||||
let! _ = ctx.Db.SaveChangesAsync ()
|
|
||||||
addHtmlInfo ctx s["The group member “{0}” was deleted successfully", mbr.Name]
|
addHtmlInfo ctx s["The group member “{0}” was deleted successfully", mbr.Name]
|
||||||
return! redirectTo false "/small-group/members" next ctx
|
return! redirectTo false "/small-group/members" next ctx
|
||||||
| Some _
|
| Some _
|
||||||
|
@ -46,7 +47,8 @@ let deleteMember mbrId : HttpHandler = requireAccess [ User ] >=> validateCsrf >
|
||||||
|
|
||||||
/// GET /small-group/[group-id]/edit
|
/// GET /small-group/[group-id]/edit
|
||||||
let edit grpId : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> task {
|
let edit grpId : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> task {
|
||||||
let! churches = ctx.Db.AllChurches ()
|
let! conn = ctx.Conn
|
||||||
|
let! churches = Churches.all conn
|
||||||
let groupId = SmallGroupId grpId
|
let groupId = SmallGroupId grpId
|
||||||
if groupId.Value = Guid.Empty then
|
if groupId.Value = Guid.Empty then
|
||||||
return!
|
return!
|
||||||
|
@ -54,7 +56,7 @@ let edit grpId : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> task
|
||||||
|> Views.SmallGroup.edit EditSmallGroup.empty churches ctx
|
|> Views.SmallGroup.edit EditSmallGroup.empty churches ctx
|
||||||
|> renderHtml next ctx
|
|> renderHtml next ctx
|
||||||
else
|
else
|
||||||
match! ctx.Db.TryGroupById groupId with
|
match! SmallGroups.tryById groupId conn with
|
||||||
| Some grp ->
|
| Some grp ->
|
||||||
return!
|
return!
|
||||||
viewInfo ctx
|
viewInfo ctx
|
||||||
|
@ -75,7 +77,8 @@ let editMember mbrId : HttpHandler = requireAccess [ User ] >=> fun next ctx ->
|
||||||
|> Views.SmallGroup.editMember EditMember.empty types ctx
|
|> Views.SmallGroup.editMember EditMember.empty types ctx
|
||||||
|> renderHtml next ctx
|
|> renderHtml next ctx
|
||||||
else
|
else
|
||||||
match! ctx.Db.TryMemberById memberId with
|
let! conn = ctx.Conn
|
||||||
|
match! Members.tryById memberId conn with
|
||||||
| Some mbr when mbr.SmallGroupId = group.Id ->
|
| Some mbr when mbr.SmallGroupId = group.Id ->
|
||||||
return!
|
return!
|
||||||
viewInfo ctx
|
viewInfo ctx
|
||||||
|
@ -87,7 +90,8 @@ let editMember mbrId : HttpHandler = requireAccess [ User ] >=> fun next ctx ->
|
||||||
|
|
||||||
/// GET /small-group/log-on/[group-id?]
|
/// GET /small-group/log-on/[group-id?]
|
||||||
let logOn grpId : HttpHandler = requireAccess [ AccessLevel.Public ] >=> fun next ctx -> task {
|
let logOn grpId : HttpHandler = requireAccess [ AccessLevel.Public ] >=> fun next ctx -> task {
|
||||||
let! groups = ctx.Db.ProtectedGroups ()
|
let! conn = ctx.Conn
|
||||||
|
let! groups = SmallGroups.listProtected conn
|
||||||
let groupId = match grpId with Some gid -> shortGuid gid | None -> ""
|
let groupId = match grpId with Some gid -> shortGuid gid | None -> ""
|
||||||
return!
|
return!
|
||||||
{ viewInfo ctx with HelpLink = Some Help.logOn }
|
{ viewInfo ctx with HelpLink = Some Help.logOn }
|
||||||
|
@ -147,20 +151,23 @@ let members : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task {
|
||||||
/// GET /small-group
|
/// GET /small-group
|
||||||
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! reqs = ctx.Db.AllRequestsForSmallGroup group ctx.Clock None true 0
|
let! reqs = ctx.Db.AllRequestsForSmallGroup group ctx.Clock None true 0
|
||||||
let! reqCount = ctx.Db.CountRequestsBySmallGroup group.Id
|
let! reqCount = ctx.Db.CountRequestsBySmallGroup group.Id
|
||||||
let! mbrCount = ctx.Db.CountMembersForSmallGroup group.Id
|
let! mbrCount = ctx.Db.CountMembersForSmallGroup group.Id
|
||||||
|
let! admins = Users.listByGroupId group.Id conn
|
||||||
let model =
|
let model =
|
||||||
{ TotalActiveReqs = List.length reqs
|
{ TotalActiveReqs = List.length reqs
|
||||||
AllReqs = reqCount
|
AllReqs = reqCount
|
||||||
TotalMembers = mbrCount
|
TotalMembers = mbrCount
|
||||||
ActiveReqsByType =
|
ActiveReqsByType = (
|
||||||
(reqs
|
reqs
|
||||||
|> Seq.ofList
|
|> Seq.ofList
|
||||||
|> Seq.map (fun req -> req.RequestType)
|
|> Seq.map (fun req -> req.RequestType)
|
||||||
|> Seq.distinct
|
|> 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)
|
|> Map.ofSeq)
|
||||||
|
Admins = admins
|
||||||
}
|
}
|
||||||
return!
|
return!
|
||||||
viewInfo ctx
|
viewInfo ctx
|
||||||
|
|
|
@ -263,25 +263,11 @@ let saveGroups : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun
|
||||||
addError ctx s["You must select at least one group to assign"]
|
addError ctx s["You must select at least one group to assign"]
|
||||||
return! redirectTo false $"/user/{model.UserId}/small-groups" next ctx
|
return! redirectTo false $"/user/{model.UserId}/small-groups" next ctx
|
||||||
| _ ->
|
| _ ->
|
||||||
match! ctx.Db.TryUserByIdWithGroups (idFromShort UserId model.UserId) with
|
let! conn = ctx.Conn
|
||||||
| Some user ->
|
do! Users.updateSmallGroups (idFromShort UserId model.UserId)
|
||||||
let groups =
|
(model.SmallGroups.Split ',' |> Array.map (idFromShort SmallGroupId) |> List.ofArray) conn
|
||||||
model.SmallGroups.Split ','
|
|
||||||
|> Array.map (idFromShort SmallGroupId)
|
|
||||||
|> List.ofArray
|
|
||||||
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.Id; SmallGroupId = x })
|
|
||||||
|> List.ofSeq
|
|
||||||
|> List.iter ctx.Db.AddEntry
|
|
||||||
let! _ = ctx.Db.SaveChangesAsync ()
|
|
||||||
addInfo ctx s["Successfully updated group permissions for {0}", model.UserName]
|
addInfo ctx s["Successfully updated group permissions for {0}", model.UserName]
|
||||||
return! redirectTo false "/users" next ctx
|
return! redirectTo false "/users" next ctx
|
||||||
| _ -> return! fourOhFour ctx
|
|
||||||
| Result.Error e -> return! bindError e next ctx
|
| Result.Error e -> return! bindError e next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,10 +275,11 @@ let saveGroups : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun
|
||||||
let smallGroups usrId : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> task {
|
let smallGroups usrId : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> task {
|
||||||
let! conn = ctx.Conn
|
let! conn = ctx.Conn
|
||||||
let userId = UserId usrId
|
let userId = UserId usrId
|
||||||
match! ctx.Db.TryUserByIdWithGroups userId with
|
match! Users.tryById userId conn with
|
||||||
| Some user ->
|
| Some user ->
|
||||||
let! groups = SmallGroups.listAll conn
|
let! groups = SmallGroups.listAll conn
|
||||||
let curGroups = user.SmallGroups |> Seq.map (fun g -> shortGuid g.SmallGroupId.Value) |> List.ofSeq
|
let! groupIds = Users.groupIdsByUserId userId conn
|
||||||
|
let curGroups = groupIds |> List.map (fun g -> shortGuid g.Value)
|
||||||
return!
|
return!
|
||||||
viewInfo ctx
|
viewInfo ctx
|
||||||
|> Views.User.assignGroups (AssignGroups.fromUser user) groups curGroups ctx
|
|> Views.User.assignGroups (AssignGroups.fromUser user) groups curGroups ctx
|
||||||
|
|
Loading…
Reference in New Issue
Block a user