Version 8 #43
| @ -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) | ||||
|             () | ||||
|     } | ||||
|  | ||||
| @ -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> | ||||
| @ -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 ] ] | ||||
|             ] | ||||
|         ] | ||||
|     ] | ||||
|  | ||||
| @ -688,6 +688,9 @@ type Overview = | ||||
|          | ||||
|         /// A count of all members | ||||
|         TotalMembers : int | ||||
|          | ||||
|         /// The users authorized to administer this group | ||||
|         Admins : User list | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -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<EditChurch> () 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 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user