PrayerTracker/src/PrayerTracker.Data/DataAccess.fs

383 lines
12 KiB
Forth
Raw Normal View History

2019-02-18 01:25:07 +00:00
[<AutoOpen>]
module PrayerTracker.DataAccess
open FSharp.Control.Tasks.ContextInsensitive
open Microsoft.EntityFrameworkCore
open PrayerTracker.Entities
open System.Collections.Generic
open System.Linq
[<AutoOpen>]
module private Helpers =
2019-08-20 19:00:27 +00:00
open Microsoft.FSharpLu
open System.Threading.Tasks
/// Central place to append sort criteria for prayer request queries
2019-08-20 19:00:27 +00:00
let reqSort sort (q : IQueryable<PrayerRequest>) =
match sort with
| SortByDate ->
2019-08-20 19:00:27 +00:00
query {
for req in q do
sortByDescending req.updatedDate
thenByDescending req.enteredDate
thenBy req.requestor
}
| SortByRequestor ->
2019-08-20 19:00:27 +00:00
query {
for req in q do
sortBy req.requestor
thenByDescending req.updatedDate
thenByDescending req.enteredDate
}
/// Convert a possibly-null object to an option, wrapped as a task
let toOptionTask<'T> (item : 'T) = (Option.fromObject >> Task.FromResult) item
2019-02-18 01:25:07 +00:00
type AppDbContext with
(*-- DISCONNECTED DATA EXTENSIONS --*)
/// Add an entity entry to the tracked data context with the status of Added
member this.AddEntry<'TEntity when 'TEntity : not struct> (e : 'TEntity) =
this.Entry<'TEntity>(e).State <- EntityState.Added
/// Add an entity entry to the tracked data context with the status of Updated
member this.UpdateEntry<'TEntity when 'TEntity : not struct> (e : 'TEntity) =
this.Entry<'TEntity>(e).State <- EntityState.Modified
/// Add an entity entry to the tracked data context with the status of Deleted
member this.RemoveEntry<'TEntity when 'TEntity : not struct> (e : 'TEntity) =
this.Entry<'TEntity>(e).State <- EntityState.Deleted
(*-- CHURCH EXTENSIONS --*)
/// Find a church by its Id
member this.TryChurchById cId =
2019-08-20 19:00:27 +00:00
query {
for ch in this.Churches.AsNoTracking () do
where (ch.churchId = cId)
exactlyOneOrDefault
2019-02-18 01:25:07 +00:00
}
2019-08-20 19:00:27 +00:00
|> toOptionTask
2019-02-18 01:25:07 +00:00
/// Find all churches
member this.AllChurches () =
task {
2019-08-20 19:00:27 +00:00
let q =
query {
for ch in this.Churches.AsNoTracking () do
sortBy ch.name
}
let! churches = q.ToListAsync ()
2019-02-18 01:25:07 +00:00
return List.ofSeq churches
}
(*-- MEMBER EXTENSIONS --*)
/// Get a small group member by its Id
member this.TryMemberById mId =
2019-08-20 19:00:27 +00:00
query {
for mbr in this.Members.AsNoTracking () do
where (mbr.memberId = mId)
select mbr
exactlyOneOrDefault
2019-02-18 01:25:07 +00:00
}
2019-08-20 19:00:27 +00:00
|> toOptionTask
2019-02-18 01:25:07 +00:00
/// Find all members for a small group
member this.AllMembersForSmallGroup gId =
task {
2019-08-20 19:00:27 +00:00
let q =
query {
for mbr in this.Members.AsNoTracking () do
where (mbr.smallGroupId = gId)
sortBy mbr.memberName
}
let! mbrs = q.ToListAsync ()
2019-02-18 01:25:07 +00:00
return List.ofSeq mbrs
}
/// Count members for a small group
member this.CountMembersForSmallGroup gId =
this.Members.CountAsync (fun m -> m.smallGroupId = gId)
(*-- PRAYER REQUEST EXTENSIONS --*)
2019-02-18 01:25:07 +00:00
/// Get a prayer request by its Id
member this.TryRequestById reqId =
2019-08-20 19:00:27 +00:00
query {
for req in this.PrayerRequests.AsNoTracking () do
where (req.prayerRequestId = reqId)
exactlyOneOrDefault
2019-02-18 01:25:07 +00:00
}
2019-08-20 19:00:27 +00:00
|> toOptionTask
2019-02-18 01:25:07 +00:00
/// Get all (or active) requests for a small group as of now or the specified date
2019-08-20 19:00:27 +00:00
// TODO: why not make this an async list like the rest of these methods?
member this.AllRequestsForSmallGroup (grp : SmallGroup) clock listDate activeOnly pageNbr : PrayerRequest seq =
2019-02-18 01:25:07 +00:00
let theDate = match listDate with Some dt -> dt | _ -> grp.localDateNow clock
2019-08-20 19:00:27 +00:00
query {
for req in this.PrayerRequests.AsNoTracking () do
where (req.smallGroupId = grp.smallGroupId)
}
|> function
| q when activeOnly ->
let asOf = theDate.AddDays(-(float grp.preferences.daysToExpire)).Date
query {
for req in q do
where ( ( req.updatedDate > asOf
|| req.expiration = Manual
|| req.requestType = LongTermRequest
|| req.requestType = Expecting)
&& req.expiration <> Forced)
}
| q -> q
|> reqSort grp.preferences.requestSort
|> function
| q ->
match activeOnly with
| true -> upcast q
| false ->
upcast query {
for req in q do
skip ((pageNbr - 1) * grp.preferences.pageSize)
take grp.preferences.pageSize
}
2019-02-18 01:25:07 +00:00
/// Count prayer requests for the given small group Id
member this.CountRequestsBySmallGroup gId =
this.PrayerRequests.CountAsync (fun pr -> pr.smallGroupId = gId)
/// Count prayer requests for the given church Id
member this.CountRequestsByChurch cId =
this.PrayerRequests.CountAsync (fun pr -> pr.smallGroup.churchId = cId)
/// Get all (or active) requests for a small group as of now or the specified date
2019-08-20 19:00:27 +00:00
// TODO: same as above...
member this.SearchRequestsForSmallGroup (grp : SmallGroup) (searchTerm : string) pageNbr : PrayerRequest seq =
2019-08-20 19:00:27 +00:00
let pgSz = grp.preferences.pageSize
let toSkip = (pageNbr - 1) * pgSz
let sql =
""" SELECT * FROM pt."PrayerRequest" WHERE "SmallGroupId" = {0} AND "Text" ILIKE {1}
UNION
SELECT * FROM pt."PrayerRequest" WHERE "SmallGroupId" = {0} AND COALESCE("Requestor", '') ILIKE {1}"""
|> RawSqlString
let like = sprintf "%%%s%%"
2019-08-20 19:00:27 +00:00
this.PrayerRequests.FromSql(sql, grp.smallGroupId, like searchTerm).AsNoTracking ()
|> reqSort grp.preferences.requestSort
|> function
| q ->
upcast query {
for req in q do
skip toSkip
take pgSz
}
2019-02-18 01:25:07 +00:00
(*-- SMALL GROUP EXTENSIONS --*)
/// Find a small group by its Id
member this.TryGroupById gId =
2019-08-20 19:00:27 +00:00
query {
for grp in this.SmallGroups.AsNoTracking().Include (fun sg -> sg.preferences) do
where (grp.smallGroupId = gId)
exactlyOneOrDefault
2019-02-18 01:25:07 +00:00
}
2019-08-20 19:00:27 +00:00
|> toOptionTask
2019-02-18 01:25:07 +00:00
/// Get small groups that are public or password protected
member this.PublicAndProtectedGroups () =
task {
2019-08-20 19:00:27 +00:00
let smallGroups = this.SmallGroups.AsNoTracking().Include(fun sg -> sg.preferences).Include (fun sg -> sg.church)
let q =
query {
for grp in smallGroups do
where ( grp.preferences.isPublic
|| (grp.preferences.groupPassword <> null && grp.preferences.groupPassword <> ""))
sortBy grp.church.name
thenBy grp.name
}
let! grps = q.ToListAsync ()
2019-02-18 01:25:07 +00:00
return List.ofSeq grps
}
/// Get small groups that are password protected
member this.ProtectedGroups () =
task {
2019-08-20 19:00:27 +00:00
let q =
query {
for grp in this.SmallGroups.AsNoTracking().Include (fun sg -> sg.church) do
where (grp.preferences.groupPassword <> null && grp.preferences.groupPassword <> "")
sortBy grp.church.name
thenBy grp.name
}
let! grps = q.ToListAsync ()
2019-02-18 01:25:07 +00:00
return List.ofSeq grps
}
/// Get all small groups
member this.AllGroups () =
task {
let! grps =
this.SmallGroups.AsNoTracking()
.Include(fun sg -> sg.church)
.Include(fun sg -> sg.preferences)
.Include(fun sg -> sg.preferences.timeZone)
.OrderBy(fun sg -> sg.name)
.ToListAsync ()
return List.ofSeq grps
}
/// Get a small group list by their Id, with their church prepended to their name
member this.GroupList () =
task {
2019-08-20 19:00:27 +00:00
let q =
query {
for grp in this.SmallGroups.AsNoTracking().Include (fun sg -> sg.church) do
sortBy grp.church.name
thenBy grp.name
}
let! grps = q.ToListAsync ()
2019-02-18 01:25:07 +00:00
return grps
|> Seq.map (fun grp -> grp.smallGroupId.ToString "N", sprintf "%s | %s" grp.church.name grp.name)
|> List.ofSeq
2019-02-18 01:25:07 +00:00
}
/// Log on a small group
member this.TryGroupLogOnByPassword gId pw =
task {
2019-08-20 19:00:27 +00:00
match! this.TryGroupById gId with
2019-02-18 01:25:07 +00:00
| None -> return None
2019-08-20 19:00:27 +00:00
| Some grp ->
match pw = grp.preferences.groupPassword with
| true -> return Some grp
2019-02-18 01:25:07 +00:00
| _ -> return None
}
/// Check a cookie log on for a small group
member this.TryGroupLogOnByCookie gId pwHash (hasher : string -> string) =
task {
2019-08-20 19:00:27 +00:00
match! this.TryGroupById gId with
2019-02-18 01:25:07 +00:00
| None -> return None
2019-08-20 19:00:27 +00:00
| Some grp ->
match pwHash = hasher grp.preferences.groupPassword with
| true -> return Some grp
2019-02-18 01:25:07 +00:00
| _ -> return None
}
/// Count small groups for the given church Id
member this.CountGroupsByChurch cId =
this.SmallGroups.CountAsync (fun sg -> sg.churchId = cId)
(*-- TIME ZONE EXTENSIONS --*)
/// Get a time zone by its Id
member this.TryTimeZoneById tzId =
2019-08-20 19:00:27 +00:00
query {
for tz in this.TimeZones do
where (tz.timeZoneId = tzId)
exactlyOneOrDefault
2019-02-18 01:25:07 +00:00
}
2019-08-20 19:00:27 +00:00
|> toOptionTask
2019-02-18 01:25:07 +00:00
/// Get all time zones
member this.AllTimeZones () =
task {
2019-08-20 19:00:27 +00:00
let q =
query {
for tz in this.TimeZones do
sortBy tz.sortOrder
}
let! tzs = q.ToListAsync ()
2019-02-18 01:25:07 +00:00
return List.ofSeq tzs
}
(*-- USER EXTENSIONS --*)
/// Find a user by its Id
member this.TryUserById uId =
2019-08-20 19:00:27 +00:00
query {
for usr in this.Users.AsNoTracking () do
where (usr.userId = uId)
exactlyOneOrDefault
2019-02-18 01:25:07 +00:00
}
2019-08-20 19:00:27 +00:00
|> toOptionTask
2019-02-18 01:25:07 +00:00
/// Find a user by its e-mail address and authorized small group
member this.TryUserByEmailAndGroup email gId =
2019-08-20 19:00:27 +00:00
query {
for usr in this.Users.AsNoTracking () do
where (usr.emailAddress = email && usr.smallGroups.Any (fun xref -> xref.smallGroupId = gId))
exactlyOneOrDefault
2019-02-18 01:25:07 +00:00
}
2019-08-20 19:00:27 +00:00
|> toOptionTask
2019-02-18 01:25:07 +00:00
/// Find a user by its Id (tracked entity), eagerly loading the user's groups
member this.TryUserByIdWithGroups uId =
2019-08-20 19:00:27 +00:00
query {
for usr in this.Users.AsNoTracking().Include (fun u -> u.smallGroups) do
where (usr.userId = uId)
exactlyOneOrDefault
2019-02-18 01:25:07 +00:00
}
2019-08-20 19:00:27 +00:00
|> toOptionTask
2019-02-18 01:25:07 +00:00
/// Get a list of all users
member this.AllUsers () =
task {
2019-08-20 19:00:27 +00:00
let q =
query {
for usr in this.Users.AsNoTracking () do
sortBy usr.lastName
thenBy usr.firstName
}
let! usrs = q.ToListAsync ()
2019-02-18 01:25:07 +00:00
return List.ofSeq usrs
}
/// Get all PrayerTracker users as members (used to send e-mails)
member this.AllUsersAsMembers () =
task {
2019-08-20 19:00:27 +00:00
let q =
query {
for usr in this.Users.AsNoTracking () do
sortBy usr.lastName
thenBy usr.firstName
select { Member.empty with email = usr.emailAddress; memberName = usr.fullName }
}
let! usrs = q.ToListAsync ()
return List.ofSeq usrs
2019-02-18 01:25:07 +00:00
}
/// Find a user based on their credentials
member this.TryUserLogOnByPassword email pwHash gId =
2019-08-20 19:00:27 +00:00
query {
for usr in this.Users.AsNoTracking () do
where ( usr.emailAddress = email
&& usr.passwordHash = pwHash
&& usr.smallGroups.Any (fun xref -> xref.smallGroupId = gId))
exactlyOneOrDefault
2019-02-18 01:25:07 +00:00
}
2019-08-20 19:00:27 +00:00
|> toOptionTask
2019-02-18 01:25:07 +00:00
/// Find a user based on credentials stored in a cookie
member this.TryUserLogOnByCookie uId gId pwHash =
task {
2019-08-20 19:00:27 +00:00
match! this.TryUserByIdWithGroups uId with
2019-02-18 01:25:07 +00:00
| None -> return None
2019-08-20 19:00:27 +00:00
| Some usr ->
match pwHash = usr.passwordHash && usr.smallGroups |> Seq.exists (fun xref -> xref.smallGroupId = gId) with
2019-02-18 01:25:07 +00:00
| true ->
2019-08-20 19:00:27 +00:00
this.Entry<User>(usr).State <- EntityState.Detached
return Some { usr with passwordHash = ""; salt = None; smallGroups = List<UserSmallGroup>() }
2019-02-18 01:25:07 +00:00
| _ -> return None
}
/// Count the number of users for a small group
member this.CountUsersBySmallGroup gId =
this.Users.CountAsync (fun u -> u.smallGroups.Any (fun xref -> xref.smallGroupId = gId))
/// Count the number of users for a church
member this.CountUsersByChurch cId =
this.Users.CountAsync (fun u -> u.smallGroups.Any (fun xref -> xref.smallGroup.churchId = cId))