Version 8 #43

Merged
danieljsummers merged 37 commits from version-8 into main 2022-08-19 19:08:31 +00:00
8 changed files with 330 additions and 95 deletions
Showing only changes of commit e621cd6a1f - Show all commits

View File

@ -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"
@ -32,16 +32,26 @@ module private Helpers =
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"
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)
()
}

View File

@ -828,4 +828,7 @@
<data name="Last Seen" xml:space="preserve">
<value>Ultima vez Visto</value>
</data>
<data name="Administrators" xml:space="preserve">
<value>Administradores</value>
</data>
</root>

View File

@ -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 ] ]
]
]
]

View File

@ -688,6 +688,9 @@ type Overview =
/// A count of all members
TotalMembers : int
/// The users authorized to administer this group
Admins : User list
}

View File

@ -1,27 +1,28 @@
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! _, 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)",
@ -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<EditChurch> () with
@ -70,12 +69,10 @@ 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 ()
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]

View File

@ -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

View File

@ -3,6 +3,7 @@
open System
open Giraffe
open PrayerTracker
open PrayerTracker.Data
open PrayerTracker.Entities
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 s = Views.I18N.localizer.Force ()
let groupId = SmallGroupId grpId
match! ctx.Db.TryGroupById groupId with
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]
@ -34,10 +35,10 @@ let deleteMember mbrId : HttpHandler = requireAccess [ User ] >=> validateCsrf >
let s = Views.I18N.localizer.Force ()
let group = ctx.Session.CurrentGroup.Value
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 ->
ctx.Db.RemoveEntry mbr
let! _ = ctx.Db.SaveChangesAsync ()
do! Members.deleteById memberId conn
addHtmlInfo ctx s["The group member &ldquo;{0}&rdquo; 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,20 +151,23 @@ 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
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

View File

@ -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 ()
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
| _ -> return! fourOhFour 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