diff --git a/src/PrayerTracker.Data/Access.fs b/src/PrayerTracker.Data/Access.fs
index 8c38286..fe3f28b 100644
--- a/src/PrayerTracker.Data/Access.fs
+++ b/src/PrayerTracker.Data/Access.fs
@@ -12,7 +12,7 @@ module private Helpers =
/// Map a row to a Church instance
let mapToChurch (row : RowReader) =
{ Id = ChurchId (row.uuid "id")
- Name = row.string "name"
+ Name = row.string "church_name"
City = row.string "city"
State = row.string "state"
HasVpsInterface = row.bool "has_vps_interface"
@@ -21,27 +21,37 @@ module private Helpers =
/// Map a row to a ListPreferences instance
let mapToListPreferences (row : RowReader) =
- { SmallGroupId = SmallGroupId (row.uuid "small_group_id")
- DaysToKeepNew = row.int "days_to_keep_new"
- DaysToExpire = row.int "days_to_expire"
- LongTermUpdateWeeks = row.int "long_term_update_weeks"
- EmailFromName = row.string "email_from_name"
- EmailFromAddress = row.string "email_from_address"
- Fonts = row.string "fonts"
- HeadingColor = row.string "heading_color"
- LineColor = row.string "line_color"
- HeadingFontSize = row.int "heading_font_size"
- TextFontSize = row.int "text_font_size"
- RequestSort = RequestSort.fromCode (row.string "request_sort")
- GroupPassword = row.string "group_password"
- DefaultEmailType = EmailFormat.fromCode (row.string "default_email_type")
- IsPublic = row.bool "is_public"
- TimeZoneId = TimeZoneId (row.string "time_zone_id")
- PageSize = row.int "page_size"
+ { SmallGroupId = SmallGroupId (row.uuid "small_group_id")
+ DaysToKeepNew = row.int "days_to_keep_new"
+ DaysToExpire = row.int "days_to_expire"
+ LongTermUpdateWeeks = row.int "long_term_update_weeks"
+ EmailFromName = row.string "email_from_name"
+ EmailFromAddress = row.string "email_from_address"
+ Fonts = row.string "fonts"
+ HeadingColor = row.string "heading_color"
+ LineColor = row.string "line_color"
+ HeadingFontSize = row.int "heading_font_size"
+ TextFontSize = row.int "text_font_size"
+ GroupPassword = row.string "group_password"
+ IsPublic = row.bool "is_public"
+ 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")
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
let mapToSmallGroup (row : RowReader) =
{ Id = SmallGroupId (row.uuid "id")
@@ -54,6 +64,10 @@ module private Helpers =
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
let mapToSmallGroupWithPreferences (row : RowReader) =
{ mapToSmallGroup row with
@@ -74,7 +88,60 @@ module private Helpers =
}
+/// Functions to manipulate 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! church =
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
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
let listAll conn =
conn
@@ -98,9 +236,33 @@ module SmallGroups =
FROM pt.small_group g
INNER JOIN pt.church c ON c.id = g.church_id
ORDER BY c.church_name, g.group_name"""
- |> Sql.executeAsync (fun row ->
- Giraffe.ShortGuid.fromGuid (row.uuid "id"), $"""{row.string "church_name"} | {row.string "group_name"}""")
+ |> Sql.executeAsync mapToSmallGroupItem
+ /// 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! group =
conn
@@ -108,7 +270,7 @@ module SmallGroups =
|> Sql.query """
SELECT sg.*, lp.*
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"""
|> Sql.parameters [ "@id", Sql.uuid groupId.Value ]
|> Sql.executeAsync mapToSmallGroupWithPreferences
@@ -126,6 +288,30 @@ module Users =
|> Sql.query "SELECT * FROM pt.pt_user ORDER BY last_name, first_name"
|> 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
let deleteById (userId : UserId) conn = backgroundTask {
let! _ =
@@ -137,6 +323,27 @@ module Users =
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
let save user conn = backgroundTask {
let! _ =
@@ -212,3 +419,27 @@ module Users =
|> Sql.executeNonQueryAsync
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)
+ ()
+ }
diff --git a/src/PrayerTracker.UI/Resources/Common.es.resx b/src/PrayerTracker.UI/Resources/Common.es.resx
index 5eb1ca7..a131275 100644
--- a/src/PrayerTracker.UI/Resources/Common.es.resx
+++ b/src/PrayerTracker.UI/Resources/Common.es.resx
@@ -828,4 +828,7 @@
Ultima vez Visto
+
+ Administradores
+
\ No newline at end of file
diff --git a/src/PrayerTracker.UI/SmallGroup.fs b/src/PrayerTracker.UI/SmallGroup.fs
index 0a13040..4207432 100644
--- a/src/PrayerTracker.UI/SmallGroup.fs
+++ b/src/PrayerTracker.UI/SmallGroup.fs
@@ -141,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 logOn (groups : (string * string) list) grpId ctx viewInfo =
let s = I18N.localizer.Force ()
let model = { SmallGroupId = emptyGuid; Password = ""; RememberMe = None }
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
else
"", selectDefault s["Select Group"].Value
- yield!
- groups
- |> List.map (fun grp -> shortGuid grp.Id.Value, $"{grp.Church.Name} | {grp.Name}")
+ yield! groups
}
|> selectList (nameof model.SmallGroupId) grpId [ _required ]
]
@@ -336,6 +334,13 @@ let overview model viewInfo =
strong [] [ str (model.TotalMembers.ToString "N0"); space; locStr s["Members"] ]
hr []
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 ] ]
]
]
]
diff --git a/src/PrayerTracker.UI/ViewModels.fs b/src/PrayerTracker.UI/ViewModels.fs
index a1118e5..0cf709a 100644
--- a/src/PrayerTracker.UI/ViewModels.fs
+++ b/src/PrayerTracker.UI/ViewModels.fs
@@ -688,6 +688,9 @@ type Overview =
/// A count of all members
TotalMembers : int
+
+ /// The users authorized to administer this group
+ Admins : User list
}
diff --git a/src/PrayerTracker/Church.fs b/src/PrayerTracker/Church.fs
index 16f14d7..e0b22e1 100644
--- a/src/PrayerTracker/Church.fs
+++ b/src/PrayerTracker/Church.fs
@@ -1,28 +1,29 @@
module PrayerTracker.Handlers.Church
+open System.Threading.Tasks
open Giraffe
open PrayerTracker
+open PrayerTracker.Data
open PrayerTracker.Entities
open PrayerTracker.ViewModels
/// 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 shortGuid churchId.Value, { SmallGroups = grps; PrayerRequests = reqs; Users = usrs }
+let private findStats churchId conn = task {
+ let! groups = SmallGroups.countByChurch churchId conn
+ let! requests = PrayerRequests.countByChurch churchId conn
+ let! users = Users.countByChurch churchId conn
+ return shortGuid churchId.Value, { SmallGroups = groups; PrayerRequests = requests; Users = users }
}
/// POST /church/[church-id]/delete
let delete chId : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next ctx -> task {
let churchId = ChurchId chId
use! conn = ctx.Conn
- match! Data.Churches.tryById churchId conn with
+ match! Churches.tryById churchId conn with
| Some church ->
- let! _, stats = findStats ctx.Db churchId
- ctx.Db.RemoveEntry church
- let! _ = ctx.Db.SaveChangesAsync ()
- let s = Views.I18N.localizer.Force ()
+ let! _, stats = findStats churchId conn
+ do! Churches.deleteById churchId conn
+ 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]
@@ -41,7 +42,7 @@ let edit churchId : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> ta
|> renderHtml next ctx
else
use! conn = ctx.Conn
- match! Data.Churches.tryById (ChurchId churchId) conn with
+ match! Churches.tryById (ChurchId churchId) conn with
| Some church ->
return!
viewInfo ctx
@@ -52,17 +53,15 @@ let edit churchId : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> ta
/// GET /churches
let maintain : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> task {
- let await = Async.AwaitTask >> Async.RunSynchronously
- let! churches = ctx.Db.AllChurches ()
- let stats = churches |> List.map (fun c -> await (findStats ctx.Db c.Id))
+ let! conn = ctx.Conn
+ let! churches = Churches.all conn
+ let stats = churches |> List.map (fun c -> findStats c.Id conn |> Async.AwaitTask |> Async.RunSynchronously)
return!
viewInfo ctx
|> 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 () with
@@ -70,14 +69,12 @@ let save : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next c
let! conn = ctx.Conn
let! church =
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
| Some ch ->
- 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 model.IsNew then "Added" else "Updated"].Value.ToLower ()
+ do! Churches.save (model.PopulateChurch ch) conn
+ let s = Views.I18N.localizer.Force ()
+ 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 ctx
diff --git a/src/PrayerTracker/Extensions.fs b/src/PrayerTracker/Extensions.fs
index 668e042..1cf8409 100644
--- a/src/PrayerTracker/Extensions.fs
+++ b/src/PrayerTracker/Extensions.fs
@@ -6,6 +6,7 @@ open Microsoft.FSharpLu
open Newtonsoft.Json
open NodaTime
open NodaTime.Serialization.JsonNet
+open PrayerTracker.Data
open PrayerTracker.Entities
open PrayerTracker.ViewModels
@@ -107,7 +108,8 @@ type HttpContext with
| None ->
match this.User.SmallGroupId with
| Some groupId ->
- match! this.Db.TryGroupById groupId with
+ let! conn = this.Conn
+ match! SmallGroups.tryByIdWithPreferences groupId conn with
| Some group ->
this.Session.CurrentGroup <- Some group
return Some group
@@ -122,11 +124,11 @@ type HttpContext with
| None ->
match this.User.UserId with
| Some userId ->
- match! this.Db.TryUserById userId with
+ let! conn = this.Conn
+ match! Users.tryById userId conn with
| Some user ->
// Set last seen for user
- this.Db.UpdateEntry { user with LastSeen = Some this.Now }
- let! _ = this.Db.SaveChangesAsync ()
+ do! Users.updateLastSeen userId this.Now conn
this.Session.CurrentUser <- Some user
return Some user
| None -> return None
diff --git a/src/PrayerTracker/SmallGroup.fs b/src/PrayerTracker/SmallGroup.fs
index d2d8803..a72b257 100644
--- a/src/PrayerTracker/SmallGroup.fs
+++ b/src/PrayerTracker/SmallGroup.fs
@@ -3,6 +3,7 @@
open System
open Giraffe
open PrayerTracker
+open PrayerTracker.Data
open PrayerTracker.Entities
open PrayerTracker.ViewModels
@@ -14,14 +15,14 @@ let announcement : HttpHandler = requireAccess [ User ] >=> fun next ctx ->
/// POST /small-group/[group-id]/delete
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
+ let s = Views.I18N.localizer.Force ()
+ let groupId = SmallGroupId grpId
+ let! conn = ctx.Conn
+ match! SmallGroups.tryById groupId conn with
| Some grp ->
- let! reqs = ctx.Db.CountRequestsBySmallGroup groupId
- let! users = ctx.Db.CountUsersBySmallGroup groupId
- ctx.Db.RemoveEntry grp
- let! _ = ctx.Db.SaveChangesAsync ()
+ let! reqs = PrayerRequests.countByGroup groupId conn
+ let! users = Users.countByGroup groupId conn
+ do! SmallGroups.deleteById groupId conn
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]
@@ -31,13 +32,13 @@ let delete grpId : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fu
/// POST /small-group/member/[member-id]/delete
let deleteMember mbrId : HttpHandler = requireAccess [ User ] >=> validateCsrf >=> fun next ctx -> task {
- let s = Views.I18N.localizer.Force ()
- let group = ctx.Session.CurrentGroup.Value
- let memberId = MemberId mbrId
- match! ctx.Db.TryMemberById memberId with
+ let s = Views.I18N.localizer.Force ()
+ let group = ctx.Session.CurrentGroup.Value
+ let memberId = MemberId mbrId
+ let! conn = ctx.Conn
+ match! Members.tryById memberId conn with
| Some mbr when mbr.SmallGroupId = group.Id ->
- ctx.Db.RemoveEntry mbr
- let! _ = ctx.Db.SaveChangesAsync ()
+ do! Members.deleteById memberId conn
addHtmlInfo ctx s["The group member “{0}” was deleted successfully", mbr.Name]
return! redirectTo false "/small-group/members" next ctx
| Some _
@@ -46,7 +47,8 @@ let deleteMember mbrId : HttpHandler = requireAccess [ User ] >=> validateCsrf >
/// GET /small-group/[group-id]/edit
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
if groupId.Value = Guid.Empty then
return!
@@ -54,7 +56,7 @@ let edit grpId : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> task
|> Views.SmallGroup.edit EditSmallGroup.empty churches ctx
|> renderHtml next ctx
else
- match! ctx.Db.TryGroupById groupId with
+ match! SmallGroups.tryById groupId conn with
| Some grp ->
return!
viewInfo ctx
@@ -75,7 +77,8 @@ let editMember mbrId : HttpHandler = requireAccess [ User ] >=> fun next ctx ->
|> Views.SmallGroup.editMember EditMember.empty types ctx
|> renderHtml next ctx
else
- match! ctx.Db.TryMemberById memberId with
+ let! conn = ctx.Conn
+ match! Members.tryById memberId conn with
| Some mbr when mbr.SmallGroupId = group.Id ->
return!
viewInfo ctx
@@ -87,7 +90,8 @@ let editMember mbrId : HttpHandler = requireAccess [ User ] >=> fun next ctx ->
/// GET /small-group/log-on/[group-id?]
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 -> ""
return!
{ viewInfo ctx with HelpLink = Some Help.logOn }
@@ -147,21 +151,24 @@ let members : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task {
/// GET /small-group
let overview : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task {
let group = ctx.Session.CurrentGroup.Value
+ let! conn = ctx.Conn
let! reqs = ctx.Db.AllRequestsForSmallGroup group ctx.Clock None true 0
let! reqCount = ctx.Db.CountRequestsBySmallGroup group.Id
let! mbrCount = ctx.Db.CountMembersForSmallGroup group.Id
+ let! admins = Users.listByGroupId group.Id conn
let model =
- { TotalActiveReqs = List.length reqs
- AllReqs = reqCount
- TotalMembers = mbrCount
- ActiveReqsByType =
- (reqs
+ { TotalActiveReqs = List.length reqs
+ AllReqs = reqCount
+ TotalMembers = mbrCount
+ ActiveReqsByType = (
+ reqs
|> Seq.ofList
|> Seq.map (fun req -> req.RequestType)
|> Seq.distinct
|> Seq.map (fun reqType -> reqType, reqs |> List.filter (fun r -> r.RequestType = reqType) |> List.length)
|> Map.ofSeq)
- }
+ Admins = admins
+ }
return!
viewInfo ctx
|> Views.SmallGroup.overview model
diff --git a/src/PrayerTracker/User.fs b/src/PrayerTracker/User.fs
index 56b46e8..9bb8435 100644
--- a/src/PrayerTracker/User.fs
+++ b/src/PrayerTracker/User.fs
@@ -263,25 +263,11 @@ let saveGroups : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun
addError ctx s["You must select at least one group to assign"]
return! redirectTo false $"/user/{model.UserId}/small-groups" next ctx
| _ ->
- match! ctx.Db.TryUserByIdWithGroups (idFromShort UserId model.UserId) with
- | Some user ->
- let groups =
- 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]
- return! redirectTo false "/users" next ctx
- | _ -> return! fourOhFour ctx
+ let! conn = ctx.Conn
+ do! Users.updateSmallGroups (idFromShort UserId model.UserId)
+ (model.SmallGroups.Split ',' |> Array.map (idFromShort SmallGroupId) |> List.ofArray) conn
+ addInfo ctx s["Successfully updated group permissions for {0}", model.UserName]
+ return! redirectTo false "/users" 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! conn = ctx.Conn
let userId = UserId usrId
- match! ctx.Db.TryUserByIdWithGroups userId with
+ match! Users.tryById userId conn with
| Some user ->
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!
viewInfo ctx
|> Views.User.assignGroups (AssignGroups.fromUser user) groups curGroups ctx