Version 8 #43
|
@ -39,7 +39,6 @@ module private Helpers =
|
||||||
RequestSort = RequestSort.fromCode (row.string "request_sort")
|
RequestSort = RequestSort.fromCode (row.string "request_sort")
|
||||||
DefaultEmailType = EmailFormat.fromCode (row.string "default_email_type")
|
DefaultEmailType = EmailFormat.fromCode (row.string "default_email_type")
|
||||||
AsOfDateDisplay = AsOfDateDisplay.fromCode (row.string "as_of_date_display")
|
AsOfDateDisplay = AsOfDateDisplay.fromCode (row.string "as_of_date_display")
|
||||||
TimeZone = TimeZone.empty
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Map a row to a Member instance
|
/// Map a row to a Member instance
|
||||||
|
@ -49,7 +48,6 @@ module private Helpers =
|
||||||
Name = row.string "member_name"
|
Name = row.string "member_name"
|
||||||
Email = row.string "email"
|
Email = row.string "email"
|
||||||
Format = row.stringOrNone "email_format" |> Option.map EmailFormat.fromCode
|
Format = row.stringOrNone "email_format" |> Option.map EmailFormat.fromCode
|
||||||
SmallGroup = SmallGroup.empty
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Map a row to a Prayer Request instance
|
/// Map a row to a Prayer Request instance
|
||||||
|
@ -64,8 +62,6 @@ module private Helpers =
|
||||||
NotifyChaplain = row.bool "notify_chaplain"
|
NotifyChaplain = row.bool "notify_chaplain"
|
||||||
RequestType = PrayerRequestType.fromCode (row.string "request_id")
|
RequestType = PrayerRequestType.fromCode (row.string "request_id")
|
||||||
Expiration = Expiration.fromCode (row.string "expiration")
|
Expiration = Expiration.fromCode (row.string "expiration")
|
||||||
User = User.empty
|
|
||||||
SmallGroup = SmallGroup.empty
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Map a row to a Small Group instance
|
/// Map a row to a Small Group instance
|
||||||
|
@ -74,10 +70,6 @@ module private Helpers =
|
||||||
ChurchId = ChurchId (row.uuid "church_id")
|
ChurchId = ChurchId (row.uuid "church_id")
|
||||||
Name = row.string "group_name"
|
Name = row.string "group_name"
|
||||||
Preferences = ListPreferences.empty
|
Preferences = ListPreferences.empty
|
||||||
Church = Church.empty
|
|
||||||
Members = ResizeArray ()
|
|
||||||
PrayerRequests = ResizeArray ()
|
|
||||||
Users = ResizeArray ()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Map a row to a Small Group information set
|
/// Map a row to a Small Group information set
|
||||||
|
@ -107,9 +99,7 @@ module private Helpers =
|
||||||
Email = row.string "email"
|
Email = row.string "email"
|
||||||
IsAdmin = row.bool "is_admin"
|
IsAdmin = row.bool "is_admin"
|
||||||
PasswordHash = row.string "password_hash"
|
PasswordHash = row.string "password_hash"
|
||||||
Salt = None
|
|
||||||
LastSeen = row.fieldValueOrNone<Instant> "last_seen"
|
LastSeen = row.fieldValueOrNone<Instant> "last_seen"
|
||||||
SmallGroups = ResizeArray ()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -373,6 +363,19 @@ module PrayerRequests =
|
||||||
return ()
|
return ()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Search prayer requests for the given term
|
||||||
|
let searchForGroup group searchTerm pageNbr conn =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query $"""
|
||||||
|
SELECT * FROM pt.prayer_request WHERE small_group_id = @groupId AND request_text ILIKE @search
|
||||||
|
UNION
|
||||||
|
SELECT * FROM pt.prayer_request WHERE small_group_id = @groupId AND COALESCE(requestor, '') ILIKE @search
|
||||||
|
ORDER BY {orderBy group.Preferences.RequestSort}
|
||||||
|
{paginate pageNbr group.Preferences.PageSize}"""
|
||||||
|
|> Sql.parameters [ "@groupId", Sql.uuid group.Id.Value; "@search", Sql.string $"%%%s{searchTerm}%%" ]
|
||||||
|
|> Sql.executeAsync mapToPrayerRequest
|
||||||
|
|
||||||
/// Retrieve a prayer request by its ID
|
/// Retrieve a prayer request by its ID
|
||||||
let tryById (reqId : PrayerRequestId) conn = backgroundTask {
|
let tryById (reqId : PrayerRequestId) conn = backgroundTask {
|
||||||
let! req =
|
let! req =
|
||||||
|
@ -385,14 +388,20 @@ module PrayerRequests =
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the expiration for the given prayer request
|
/// Update the expiration for the given prayer request
|
||||||
let updateExpiration (req : PrayerRequest) conn = backgroundTask {
|
let updateExpiration (req : PrayerRequest) withTime conn = backgroundTask {
|
||||||
|
let sql, parameters =
|
||||||
|
if withTime then
|
||||||
|
", updated_date = @updated",
|
||||||
|
[ "@updated", Sql.parameter (NpgsqlParameter ("@updated", req.UpdatedDate)) ]
|
||||||
|
else "", []
|
||||||
let! _ =
|
let! _ =
|
||||||
conn
|
conn
|
||||||
|> Sql.existingConnection
|
|> Sql.existingConnection
|
||||||
|> Sql.query "UPDATE pt.prayer_request SET expiration = @expiration WHERE id = @id"
|
|> Sql.query $"UPDATE pt.prayer_request SET expiration = @expiration{sql} WHERE id = @id"
|
||||||
|> Sql.parameters
|
|> Sql.parameters
|
||||||
[ "@expiration", Sql.string (Expiration.toCode req.Expiration)
|
([ "@expiration", Sql.string (Expiration.toCode req.Expiration)
|
||||||
"@id", Sql.uuid req.Id.Value ]
|
"@id", Sql.uuid req.Id.Value ]
|
||||||
|
|> List.append parameters)
|
||||||
|> Sql.executeNonQueryAsync
|
|> Sql.executeNonQueryAsync
|
||||||
return ()
|
return ()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,86 +0,0 @@
|
||||||
namespace PrayerTracker
|
|
||||||
|
|
||||||
open Microsoft.EntityFrameworkCore
|
|
||||||
open PrayerTracker.Entities
|
|
||||||
|
|
||||||
/// EF Core data context for PrayerTracker
|
|
||||||
[<AllowNullLiteral>]
|
|
||||||
type AppDbContext (options : DbContextOptions<AppDbContext>) =
|
|
||||||
inherit DbContext (options)
|
|
||||||
|
|
||||||
[<DefaultValue>]
|
|
||||||
val mutable private churches : DbSet<Church>
|
|
||||||
[<DefaultValue>]
|
|
||||||
val mutable private members : DbSet<Member>
|
|
||||||
[<DefaultValue>]
|
|
||||||
val mutable private prayerRequests : DbSet<PrayerRequest>
|
|
||||||
[<DefaultValue>]
|
|
||||||
val mutable private preferences : DbSet<ListPreferences>
|
|
||||||
[<DefaultValue>]
|
|
||||||
val mutable private smallGroups : DbSet<SmallGroup>
|
|
||||||
[<DefaultValue>]
|
|
||||||
val mutable private timeZones : DbSet<TimeZone>
|
|
||||||
[<DefaultValue>]
|
|
||||||
val mutable private users : DbSet<User>
|
|
||||||
[<DefaultValue>]
|
|
||||||
val mutable private userGroupXref : DbSet<UserSmallGroup>
|
|
||||||
|
|
||||||
/// Churches
|
|
||||||
member this.Churches
|
|
||||||
with get() = this.churches
|
|
||||||
and set v = this.churches <- v
|
|
||||||
|
|
||||||
/// Small group members
|
|
||||||
member this.Members
|
|
||||||
with get() = this.members
|
|
||||||
and set v = this.members <- v
|
|
||||||
|
|
||||||
/// Prayer requests
|
|
||||||
member this.PrayerRequests
|
|
||||||
with get() = this.prayerRequests
|
|
||||||
and set v = this.prayerRequests <- v
|
|
||||||
|
|
||||||
/// Request list preferences (by class)
|
|
||||||
member this.Preferences
|
|
||||||
with get() = this.preferences
|
|
||||||
and set v = this.preferences <- v
|
|
||||||
|
|
||||||
/// Small groups
|
|
||||||
member this.SmallGroups
|
|
||||||
with get() = this.smallGroups
|
|
||||||
and set v = this.smallGroups <- v
|
|
||||||
|
|
||||||
/// Time zones
|
|
||||||
member this.TimeZones
|
|
||||||
with get() = this.timeZones
|
|
||||||
and set v = this.timeZones <- v
|
|
||||||
|
|
||||||
/// Users
|
|
||||||
member this.Users
|
|
||||||
with get() = this.users
|
|
||||||
and set v = this.users <- v
|
|
||||||
|
|
||||||
/// User / small group cross-reference
|
|
||||||
member this.UserGroupXref
|
|
||||||
with get() = this.userGroupXref
|
|
||||||
and set v = this.userGroupXref <- v
|
|
||||||
|
|
||||||
override _.OnConfiguring (optionsBuilder : DbContextOptionsBuilder) =
|
|
||||||
base.OnConfiguring optionsBuilder
|
|
||||||
optionsBuilder.UseQueryTrackingBehavior QueryTrackingBehavior.NoTracking |> ignore
|
|
||||||
|
|
||||||
override _.OnModelCreating (modelBuilder : ModelBuilder) =
|
|
||||||
base.OnModelCreating modelBuilder
|
|
||||||
|
|
||||||
modelBuilder.HasDefaultSchema "pt" |> ignore
|
|
||||||
|
|
||||||
[ Church.ConfigureEF
|
|
||||||
ListPreferences.ConfigureEF
|
|
||||||
Member.ConfigureEF
|
|
||||||
PrayerRequest.ConfigureEF
|
|
||||||
SmallGroup.ConfigureEF
|
|
||||||
TimeZone.ConfigureEF
|
|
||||||
User.ConfigureEF
|
|
||||||
UserSmallGroup.ConfigureEF
|
|
||||||
]
|
|
||||||
|> List.iter (fun x -> x modelBuilder)
|
|
|
@ -1,257 +0,0 @@
|
||||||
[<AutoOpen>]
|
|
||||||
module PrayerTracker.DataAccess
|
|
||||||
|
|
||||||
open System.Linq
|
|
||||||
open NodaTime
|
|
||||||
open PrayerTracker.Entities
|
|
||||||
|
|
||||||
[<AutoOpen>]
|
|
||||||
module private Helpers =
|
|
||||||
|
|
||||||
/// Central place to append sort criteria for prayer request queries
|
|
||||||
let reqSort sort (q : IQueryable<PrayerRequest>) =
|
|
||||||
match sort with
|
|
||||||
| SortByDate ->
|
|
||||||
q.OrderByDescending(fun req -> req.UpdatedDate)
|
|
||||||
.ThenByDescending(fun req -> req.EnteredDate)
|
|
||||||
.ThenBy (fun req -> req.Requestor)
|
|
||||||
| SortByRequestor ->
|
|
||||||
q.OrderBy(fun req -> req.Requestor)
|
|
||||||
.ThenByDescending(fun req -> req.UpdatedDate)
|
|
||||||
.ThenByDescending (fun req -> req.EnteredDate)
|
|
||||||
|
|
||||||
/// Paginate a prayer request query
|
|
||||||
let paginate (pageNbr : int) pageSize (q : IQueryable<PrayerRequest>) =
|
|
||||||
if pageNbr > 0 then q.Skip((pageNbr - 1) * pageSize).Take pageSize else q
|
|
||||||
|
|
||||||
|
|
||||||
open Microsoft.EntityFrameworkCore
|
|
||||||
open Microsoft.FSharpLu
|
|
||||||
|
|
||||||
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 churchId = backgroundTask {
|
|
||||||
let! church = this.Churches.SingleOrDefaultAsync (fun ch -> ch.Id = churchId)
|
|
||||||
return Option.fromObject church
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find all churches
|
|
||||||
member this.AllChurches () = backgroundTask {
|
|
||||||
let! churches = this.Churches.OrderBy(fun ch -> ch.Name).ToListAsync ()
|
|
||||||
return List.ofSeq churches
|
|
||||||
}
|
|
||||||
|
|
||||||
(*-- MEMBER EXTENSIONS --*)
|
|
||||||
|
|
||||||
/// Get a small group member by its Id
|
|
||||||
member this.TryMemberById memberId = backgroundTask {
|
|
||||||
let! mbr = this.Members.SingleOrDefaultAsync (fun m -> m.Id = memberId)
|
|
||||||
return Option.fromObject mbr
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find all members for a small group
|
|
||||||
member this.AllMembersForSmallGroup groupId = backgroundTask {
|
|
||||||
let! members =
|
|
||||||
this.Members.Where(fun mbr -> mbr.SmallGroupId = groupId)
|
|
||||||
.OrderBy(fun mbr -> mbr.Name)
|
|
||||||
.ToListAsync ()
|
|
||||||
return List.ofSeq members
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Count members for a small group
|
|
||||||
member this.CountMembersForSmallGroup groupId = backgroundTask {
|
|
||||||
return! this.Members.CountAsync (fun m -> m.SmallGroupId = groupId)
|
|
||||||
}
|
|
||||||
|
|
||||||
(*-- PRAYER REQUEST EXTENSIONS --*)
|
|
||||||
|
|
||||||
/// Get a prayer request by its Id
|
|
||||||
member this.TryRequestById reqId = backgroundTask {
|
|
||||||
let! req = this.PrayerRequests.SingleOrDefaultAsync (fun r -> r.Id = reqId)
|
|
||||||
return Option.fromObject req
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get all (or active) requests for a small group as of now or the specified date
|
|
||||||
member this.AllRequestsForSmallGroup (grp : SmallGroup) clock listDate activeOnly pageNbr = backgroundTask {
|
|
||||||
let theDate = match listDate with Some dt -> dt | _ -> SmallGroup.localDateNow clock grp
|
|
||||||
let query =
|
|
||||||
this.PrayerRequests.Where(fun req -> req.SmallGroupId = grp.Id)
|
|
||||||
|> function
|
|
||||||
| q when activeOnly ->
|
|
||||||
let asOf =
|
|
||||||
(theDate.AtStartOfDayInZone(SmallGroup.timeZone grp) - Duration.FromDays grp.Preferences.DaysToExpire)
|
|
||||||
.ToInstant ()
|
|
||||||
q.Where(fun req ->
|
|
||||||
( req.UpdatedDate > asOf
|
|
||||||
|| req.Expiration = Manual
|
|
||||||
|| req.RequestType = LongTermRequest
|
|
||||||
|| req.RequestType = Expecting)
|
|
||||||
&& req.Expiration <> Forced)
|
|
||||||
|> reqSort grp.Preferences.RequestSort
|
|
||||||
|> paginate pageNbr grp.Preferences.PageSize
|
|
||||||
| q -> reqSort grp.Preferences.RequestSort q
|
|
||||||
let! reqs = query.ToListAsync ()
|
|
||||||
return List.ofSeq reqs
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Count prayer requests for the given small group Id
|
|
||||||
member this.CountRequestsBySmallGroup groupId = backgroundTask {
|
|
||||||
return! this.PrayerRequests.CountAsync (fun pr -> pr.SmallGroupId = groupId)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Count prayer requests for the given church Id
|
|
||||||
member this.CountRequestsByChurch churchId = backgroundTask {
|
|
||||||
return! this.PrayerRequests.CountAsync (fun pr -> pr.SmallGroup.ChurchId = churchId)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Search requests for a small group using the given case-insensitive search term
|
|
||||||
member this.SearchRequestsForSmallGroup (grp : SmallGroup) (searchTerm : string) pageNbr = backgroundTask {
|
|
||||||
let sql = """
|
|
||||||
SELECT * FROM pt.prayer_request WHERE small_group_id = {0} AND request_text ILIKE {1}
|
|
||||||
UNION
|
|
||||||
SELECT * FROM pt.prayer_request WHERE small_group_id = {0} AND COALESCE(requestor, '') ILIKE {1}"""
|
|
||||||
let like = sprintf "%%%s%%"
|
|
||||||
let query =
|
|
||||||
this.PrayerRequests.FromSqlRaw (sql, grp.Id.Value, like searchTerm)
|
|
||||||
|> reqSort grp.Preferences.RequestSort
|
|
||||||
|> paginate pageNbr grp.Preferences.PageSize
|
|
||||||
let! reqs = query.ToListAsync ()
|
|
||||||
return List.ofSeq reqs
|
|
||||||
}
|
|
||||||
|
|
||||||
(*-- SMALL GROUP EXTENSIONS --*)
|
|
||||||
|
|
||||||
/// Find a small group by its Id
|
|
||||||
member this.TryGroupById groupId = backgroundTask {
|
|
||||||
let! grp =
|
|
||||||
this.SmallGroups.Include(fun sg -> sg.Preferences)
|
|
||||||
.SingleOrDefaultAsync (fun sg -> sg.Id = groupId)
|
|
||||||
return Option.fromObject grp
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get small groups that are public or password protected
|
|
||||||
member this.PublicAndProtectedGroups () = backgroundTask {
|
|
||||||
let! groups =
|
|
||||||
this.SmallGroups.Include(fun sg -> sg.Preferences).Include(fun sg -> sg.Church)
|
|
||||||
.Where(fun sg ->
|
|
||||||
sg.Preferences.IsPublic
|
|
||||||
|| (sg.Preferences.GroupPassword <> null && sg.Preferences.GroupPassword <> ""))
|
|
||||||
.OrderBy(fun sg -> sg.Church.Name).ThenBy(fun sg -> sg.Name)
|
|
||||||
.ToListAsync ()
|
|
||||||
return List.ofSeq groups
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get small groups that are password protected
|
|
||||||
member this.ProtectedGroups () = backgroundTask {
|
|
||||||
let! groups =
|
|
||||||
this.SmallGroups.Include(fun sg -> sg.Church)
|
|
||||||
.Where(fun sg -> sg.Preferences.GroupPassword <> null && sg.Preferences.GroupPassword <> "")
|
|
||||||
.OrderBy(fun sg -> sg.Church.Name).ThenBy(fun sg -> sg.Name)
|
|
||||||
.ToListAsync ()
|
|
||||||
return List.ofSeq groups
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get all small groups
|
|
||||||
member this.AllGroups () = backgroundTask {
|
|
||||||
let! groups =
|
|
||||||
this.SmallGroups
|
|
||||||
.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 groups
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a small group list by their Id, with their church prepended to their name
|
|
||||||
member this.GroupList () = backgroundTask {
|
|
||||||
let! groups =
|
|
||||||
this.SmallGroups.Include(fun sg -> sg.Church)
|
|
||||||
.OrderBy(fun sg -> sg.Church.Name).ThenBy(fun sg -> sg.Name)
|
|
||||||
.ToListAsync ()
|
|
||||||
return
|
|
||||||
groups
|
|
||||||
|> Seq.map (fun sg -> Giraffe.ShortGuid.fromGuid sg.Id.Value, $"{sg.Church.Name} | {sg.Name}")
|
|
||||||
|> List.ofSeq
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Log on a small group
|
|
||||||
member this.TryGroupLogOnByPassword groupId pw = backgroundTask {
|
|
||||||
match! this.TryGroupById groupId with
|
|
||||||
| Some grp when pw = grp.Preferences.GroupPassword -> return Some grp
|
|
||||||
| _ -> return None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Count small groups for the given church Id
|
|
||||||
member this.CountGroupsByChurch churchId = backgroundTask {
|
|
||||||
return! this.SmallGroups.CountAsync (fun sg -> sg.ChurchId = churchId)
|
|
||||||
}
|
|
||||||
|
|
||||||
(*-- TIME ZONE EXTENSIONS --*)
|
|
||||||
|
|
||||||
/// Get all time zones
|
|
||||||
member this.AllTimeZones () = backgroundTask {
|
|
||||||
let! zones = this.TimeZones.OrderBy(fun tz -> tz.SortOrder).ToListAsync ()
|
|
||||||
return List.ofSeq zones
|
|
||||||
}
|
|
||||||
|
|
||||||
(*-- USER EXTENSIONS --*)
|
|
||||||
|
|
||||||
/// Find a user by its Id
|
|
||||||
member this.TryUserById userId = backgroundTask {
|
|
||||||
let! usr = this.Users.SingleOrDefaultAsync (fun u -> u.Id = userId)
|
|
||||||
return Option.fromObject usr
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find a user by its e-mail address and authorized small group
|
|
||||||
member this.TryUserByEmailAndGroup email groupId = backgroundTask {
|
|
||||||
let! usr =
|
|
||||||
this.Users.SingleOrDefaultAsync (fun u ->
|
|
||||||
u.Email = email && u.SmallGroups.Any (fun xref -> xref.SmallGroupId = groupId))
|
|
||||||
return Option.fromObject usr
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find a user by its Id, eagerly loading the user's groups
|
|
||||||
member this.TryUserByIdWithGroups userId = backgroundTask {
|
|
||||||
let! usr = this.Users.Include(fun u -> u.SmallGroups).SingleOrDefaultAsync (fun u -> u.Id = userId)
|
|
||||||
return Option.fromObject usr
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a list of all users
|
|
||||||
member this.AllUsers () = backgroundTask {
|
|
||||||
let! users = this.Users.OrderBy(fun u -> u.LastName).ThenBy(fun u -> u.FirstName).ToListAsync ()
|
|
||||||
return List.ofSeq users
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get all PrayerTracker users as members (used to send e-mails)
|
|
||||||
member this.AllUsersAsMembers () = backgroundTask {
|
|
||||||
let! users = this.AllUsers ()
|
|
||||||
return users |> List.map (fun u -> { Member.empty with Email = u.Email; Name = u.Name })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Count the number of users for a small group
|
|
||||||
member this.CountUsersBySmallGroup groupId = backgroundTask {
|
|
||||||
return! this.Users.CountAsync (fun u -> u.SmallGroups.Any (fun xref -> xref.SmallGroupId = groupId))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Count the number of users for a church
|
|
||||||
member this.CountUsersByChurch churchId = backgroundTask {
|
|
||||||
return! this.Users.CountAsync (fun u -> u.SmallGroups.Any (fun xref -> xref.SmallGroup.ChurchId = churchId))
|
|
||||||
}
|
|
|
@ -179,184 +179,7 @@ with
|
||||||
/// The GUID value of the user ID
|
/// The GUID value of the user ID
|
||||||
member this.Value = this |> function UserId guid -> guid
|
member this.Value = this |> function UserId guid -> guid
|
||||||
|
|
||||||
|
(*-- SPECIFIC VIEW TYPES --*)
|
||||||
/// EF Core value converters for the discriminated union types above
|
|
||||||
module Converters =
|
|
||||||
|
|
||||||
open Microsoft.EntityFrameworkCore.Storage.ValueConversion
|
|
||||||
open Microsoft.FSharp.Linq.RuntimeHelpers
|
|
||||||
open System.Linq.Expressions
|
|
||||||
|
|
||||||
let private asOfFromDU =
|
|
||||||
<@ Func<AsOfDateDisplay, string>(AsOfDateDisplay.toCode) @>
|
|
||||||
|> LeafExpressionConverter.QuotationToExpression
|
|
||||||
|> unbox<Expression<Func<AsOfDateDisplay, string>>>
|
|
||||||
|
|
||||||
let private asOfToDU =
|
|
||||||
<@ Func<string, AsOfDateDisplay>(AsOfDateDisplay.fromCode) @>
|
|
||||||
|> LeafExpressionConverter.QuotationToExpression
|
|
||||||
|> unbox<Expression<Func<string, AsOfDateDisplay>>>
|
|
||||||
|
|
||||||
let private churchIdFromDU =
|
|
||||||
<@ Func<ChurchId, Guid>(fun it -> it.Value) @>
|
|
||||||
|> LeafExpressionConverter.QuotationToExpression
|
|
||||||
|> unbox<Expression<Func<ChurchId, Guid>>>
|
|
||||||
|
|
||||||
let private churchIdToDU =
|
|
||||||
<@ Func<Guid, ChurchId>(ChurchId) @>
|
|
||||||
|> LeafExpressionConverter.QuotationToExpression
|
|
||||||
|> unbox<Expression<Func<Guid, ChurchId>>>
|
|
||||||
|
|
||||||
let private emailFromDU =
|
|
||||||
<@ Func<EmailFormat, string>(EmailFormat.toCode) @>
|
|
||||||
|> LeafExpressionConverter.QuotationToExpression
|
|
||||||
|> unbox<Expression<Func<EmailFormat, string>>>
|
|
||||||
|
|
||||||
let private emailToDU =
|
|
||||||
<@ Func<string, EmailFormat>(EmailFormat.fromCode) @>
|
|
||||||
|> LeafExpressionConverter.QuotationToExpression
|
|
||||||
|> unbox<Expression<Func<string, EmailFormat>>>
|
|
||||||
|
|
||||||
let private emailOptionFromDU =
|
|
||||||
<@ Func<EmailFormat option, string>(fun opt ->
|
|
||||||
match opt with Some fmt -> EmailFormat.toCode fmt | None -> null) @>
|
|
||||||
|> LeafExpressionConverter.QuotationToExpression
|
|
||||||
|> unbox<Expression<Func<EmailFormat option, string>>>
|
|
||||||
|
|
||||||
let private emailOptionToDU =
|
|
||||||
<@ Func<string, EmailFormat option>(fun opt ->
|
|
||||||
match opt with "" | null -> None | it -> Some (EmailFormat.fromCode it)) @>
|
|
||||||
|> LeafExpressionConverter.QuotationToExpression
|
|
||||||
|> unbox<Expression<Func<string, EmailFormat option>>>
|
|
||||||
|
|
||||||
let private expFromDU =
|
|
||||||
<@ Func<Expiration, string>(Expiration.toCode) @>
|
|
||||||
|> LeafExpressionConverter.QuotationToExpression
|
|
||||||
|> unbox<Expression<Func<Expiration, string>>>
|
|
||||||
|
|
||||||
let private expToDU =
|
|
||||||
<@ Func<string, Expiration>(Expiration.fromCode) @>
|
|
||||||
|> LeafExpressionConverter.QuotationToExpression
|
|
||||||
|> unbox<Expression<Func<string, Expiration>>>
|
|
||||||
|
|
||||||
let private memberIdFromDU =
|
|
||||||
<@ Func<MemberId, Guid>(fun it -> it.Value) @>
|
|
||||||
|> LeafExpressionConverter.QuotationToExpression
|
|
||||||
|> unbox<Expression<Func<MemberId, Guid>>>
|
|
||||||
|
|
||||||
let private memberIdToDU =
|
|
||||||
<@ Func<Guid, MemberId>(MemberId) @>
|
|
||||||
|> LeafExpressionConverter.QuotationToExpression
|
|
||||||
|> unbox<Expression<Func<Guid, MemberId>>>
|
|
||||||
|
|
||||||
let private prayerReqIdFromDU =
|
|
||||||
<@ Func<PrayerRequestId, Guid>(fun it -> it.Value) @>
|
|
||||||
|> LeafExpressionConverter.QuotationToExpression
|
|
||||||
|> unbox<Expression<Func<PrayerRequestId, Guid>>>
|
|
||||||
|
|
||||||
let private prayerReqIdToDU =
|
|
||||||
<@ Func<Guid, PrayerRequestId>(PrayerRequestId) @>
|
|
||||||
|> LeafExpressionConverter.QuotationToExpression
|
|
||||||
|> unbox<Expression<Func<Guid, PrayerRequestId>>>
|
|
||||||
|
|
||||||
let private smallGrpIdFromDU =
|
|
||||||
<@ Func<SmallGroupId, Guid>(fun it -> it.Value) @>
|
|
||||||
|> LeafExpressionConverter.QuotationToExpression
|
|
||||||
|> unbox<Expression<Func<SmallGroupId, Guid>>>
|
|
||||||
|
|
||||||
let private smallGrpIdToDU =
|
|
||||||
<@ Func<Guid, SmallGroupId>(SmallGroupId) @>
|
|
||||||
|> LeafExpressionConverter.QuotationToExpression
|
|
||||||
|> unbox<Expression<Func<Guid, SmallGroupId>>>
|
|
||||||
|
|
||||||
let private sortFromDU =
|
|
||||||
<@ Func<RequestSort, string>(RequestSort.toCode) @>
|
|
||||||
|> LeafExpressionConverter.QuotationToExpression
|
|
||||||
|> unbox<Expression<Func<RequestSort, string>>>
|
|
||||||
|
|
||||||
let private sortToDU =
|
|
||||||
<@ Func<string, RequestSort>(RequestSort.fromCode) @>
|
|
||||||
|> LeafExpressionConverter.QuotationToExpression
|
|
||||||
|> unbox<Expression<Func<string, RequestSort>>>
|
|
||||||
|
|
||||||
let private typFromDU =
|
|
||||||
<@ Func<PrayerRequestType, string>(PrayerRequestType.toCode) @>
|
|
||||||
|> LeafExpressionConverter.QuotationToExpression
|
|
||||||
|> unbox<Expression<Func<PrayerRequestType, string>>>
|
|
||||||
|
|
||||||
let private typToDU =
|
|
||||||
<@ Func<string, PrayerRequestType>(PrayerRequestType.fromCode) @>
|
|
||||||
|> LeafExpressionConverter.QuotationToExpression
|
|
||||||
|> unbox<Expression<Func<string, PrayerRequestType>>>
|
|
||||||
|
|
||||||
let private tzIdFromDU =
|
|
||||||
<@ Func<TimeZoneId, string>(TimeZoneId.toString) @>
|
|
||||||
|> LeafExpressionConverter.QuotationToExpression
|
|
||||||
|> unbox<Expression<Func<TimeZoneId, string>>>
|
|
||||||
|
|
||||||
let private tzIdToDU =
|
|
||||||
<@ Func<string, TimeZoneId>(TimeZoneId) @>
|
|
||||||
|> LeafExpressionConverter.QuotationToExpression
|
|
||||||
|> unbox<Expression<Func<string, TimeZoneId>>>
|
|
||||||
|
|
||||||
let private userIdFromDU =
|
|
||||||
<@ Func<UserId, Guid>(fun it -> it.Value) @>
|
|
||||||
|> LeafExpressionConverter.QuotationToExpression
|
|
||||||
|> unbox<Expression<Func<UserId, Guid>>>
|
|
||||||
|
|
||||||
let private userIdToDU =
|
|
||||||
<@ Func<Guid, UserId>(UserId) @>
|
|
||||||
|> LeafExpressionConverter.QuotationToExpression
|
|
||||||
|> unbox<Expression<Func<Guid, UserId>>>
|
|
||||||
|
|
||||||
/// Conversion between a string and an AsOfDateDisplay DU value
|
|
||||||
type AsOfDateDisplayConverter () =
|
|
||||||
inherit ValueConverter<AsOfDateDisplay, string> (asOfFromDU, asOfToDU)
|
|
||||||
|
|
||||||
/// Conversion between a GUID and a church ID
|
|
||||||
type ChurchIdConverter () =
|
|
||||||
inherit ValueConverter<ChurchId, Guid> (churchIdFromDU, churchIdToDU)
|
|
||||||
|
|
||||||
/// Conversion between a string and an EmailFormat DU value
|
|
||||||
type EmailFormatConverter () =
|
|
||||||
inherit ValueConverter<EmailFormat, string> (emailFromDU, emailToDU)
|
|
||||||
|
|
||||||
/// Conversion between a string an an optional EmailFormat DU value
|
|
||||||
type EmailFormatOptionConverter () =
|
|
||||||
inherit ValueConverter<EmailFormat option, string> (emailOptionFromDU, emailOptionToDU)
|
|
||||||
|
|
||||||
/// Conversion between a string and an Expiration DU value
|
|
||||||
type ExpirationConverter () =
|
|
||||||
inherit ValueConverter<Expiration, string> (expFromDU, expToDU)
|
|
||||||
|
|
||||||
/// Conversion between a GUID and a member ID
|
|
||||||
type MemberIdConverter () =
|
|
||||||
inherit ValueConverter<MemberId, Guid> (memberIdFromDU, memberIdToDU)
|
|
||||||
|
|
||||||
/// Conversion between a GUID and a prayer request ID
|
|
||||||
type PrayerRequestIdConverter () =
|
|
||||||
inherit ValueConverter<PrayerRequestId, Guid> (prayerReqIdFromDU, prayerReqIdToDU)
|
|
||||||
|
|
||||||
/// Conversion between a string and a PrayerRequestType DU value
|
|
||||||
type PrayerRequestTypeConverter () =
|
|
||||||
inherit ValueConverter<PrayerRequestType, string> (typFromDU, typToDU)
|
|
||||||
|
|
||||||
/// Conversion between a string and a RequestSort DU value
|
|
||||||
type RequestSortConverter () =
|
|
||||||
inherit ValueConverter<RequestSort, string> (sortFromDU, sortToDU)
|
|
||||||
|
|
||||||
/// Conversion between a GUID and a small group ID
|
|
||||||
type SmallGroupIdConverter () =
|
|
||||||
inherit ValueConverter<SmallGroupId, Guid> (smallGrpIdFromDU, smallGrpIdToDU)
|
|
||||||
|
|
||||||
/// Conversion between a string and a time zone ID
|
|
||||||
type TimeZoneIdConverter () =
|
|
||||||
inherit ValueConverter<TimeZoneId, string> (tzIdFromDU, tzIdToDU)
|
|
||||||
|
|
||||||
/// Conversion between a GUID and a user ID
|
|
||||||
type UserIdConverter () =
|
|
||||||
inherit ValueConverter<UserId, Guid> (userIdFromDU, userIdToDU)
|
|
||||||
|
|
||||||
|
|
||||||
/// Statistics for churches
|
/// Statistics for churches
|
||||||
[<NoComparison; NoEquality>]
|
[<NoComparison; NoEquality>]
|
||||||
|
@ -371,14 +194,33 @@ type ChurchStats =
|
||||||
Users : int
|
Users : int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Information needed to display the public/protected request list and small group maintenance pages
|
||||||
|
[<NoComparison; NoEquality>]
|
||||||
|
type SmallGroupInfo =
|
||||||
|
{ /// The ID of the small group
|
||||||
|
Id : string
|
||||||
|
|
||||||
|
/// The name of the small group
|
||||||
|
Name : string
|
||||||
|
|
||||||
|
/// The name of the church to which the small group belongs
|
||||||
|
ChurchName : string
|
||||||
|
|
||||||
|
/// The ID of the time zone for the small group
|
||||||
|
TimeZoneId : TimeZoneId
|
||||||
|
|
||||||
|
/// Whether the small group has a publicly-available request list
|
||||||
|
IsPublic : bool
|
||||||
|
}
|
||||||
|
|
||||||
(*-- ENTITIES --*)
|
(*-- ENTITIES --*)
|
||||||
|
|
||||||
open FSharp.EFCore.OptionConverter
|
|
||||||
open Microsoft.EntityFrameworkCore
|
|
||||||
open NodaTime
|
open NodaTime
|
||||||
|
|
||||||
/// This represents a church
|
/// This represents a church
|
||||||
type [<CLIMutable; NoComparison; NoEquality>] Church =
|
[<NoComparison; NoEquality>]
|
||||||
|
type Church =
|
||||||
{ /// The ID of this church
|
{ /// The ID of this church
|
||||||
Id : ChurchId
|
Id : ChurchId
|
||||||
|
|
||||||
|
@ -397,10 +239,13 @@ type [<CLIMutable; NoComparison; NoEquality>] Church =
|
||||||
/// The address for the interface
|
/// The address for the interface
|
||||||
InterfaceAddress : string option
|
InterfaceAddress : string option
|
||||||
}
|
}
|
||||||
with
|
|
||||||
|
/// Functions to support churches
|
||||||
|
module Church =
|
||||||
|
|
||||||
/// An empty church
|
/// An empty church
|
||||||
// aww... how sad :(
|
// aww... how sad :(
|
||||||
static member empty =
|
let empty =
|
||||||
{ Id = ChurchId Guid.Empty
|
{ Id = ChurchId Guid.Empty
|
||||||
Name = ""
|
Name = ""
|
||||||
City = ""
|
City = ""
|
||||||
|
@ -409,27 +254,10 @@ with
|
||||||
InterfaceAddress = None
|
InterfaceAddress = None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configure EF for this entity
|
|
||||||
static member internal ConfigureEF (mb : ModelBuilder) =
|
|
||||||
mb.Entity<Church> (fun it ->
|
|
||||||
seq<obj> {
|
|
||||||
it.ToTable "church"
|
|
||||||
it.Property(fun c -> c.Id).HasColumnName "id"
|
|
||||||
it.Property(fun c -> c.Name).HasColumnName("church_name").IsRequired ()
|
|
||||||
it.Property(fun c -> c.City).HasColumnName("city").IsRequired ()
|
|
||||||
it.Property(fun c -> c.State).HasColumnName("state").IsRequired().HasMaxLength 2
|
|
||||||
it.Property(fun c -> c.HasVpsInterface).HasColumnName "has_vps_interface"
|
|
||||||
it.Property(fun c -> c.InterfaceAddress).HasColumnName "interface_address"
|
|
||||||
} |> List.ofSeq |> ignore)
|
|
||||||
|> ignore
|
|
||||||
mb.Model.FindEntityType(typeof<Church>).FindProperty(nameof Church.empty.Id)
|
|
||||||
.SetValueConverter (Converters.ChurchIdConverter ())
|
|
||||||
mb.Model.FindEntityType(typeof<Church>).FindProperty(nameof Church.empty.InterfaceAddress)
|
|
||||||
.SetValueConverter (OptionConverter<string> ())
|
|
||||||
|
|
||||||
|
|
||||||
/// Preferences for the form and format of the prayer request list
|
/// Preferences for the form and format of the prayer request list
|
||||||
and [<CLIMutable; NoComparison; NoEquality>] ListPreferences =
|
[<NoComparison; NoEquality>]
|
||||||
|
type ListPreferences =
|
||||||
{ /// The Id of the small group to which these preferences belong
|
{ /// The Id of the small group to which these preferences belong
|
||||||
SmallGroupId : SmallGroupId
|
SmallGroupId : SmallGroupId
|
||||||
|
|
||||||
|
@ -478,19 +306,18 @@ and [<CLIMutable; NoComparison; NoEquality>] ListPreferences =
|
||||||
/// The time zone which this class uses (use tzdata names)
|
/// The time zone which this class uses (use tzdata names)
|
||||||
TimeZoneId : TimeZoneId
|
TimeZoneId : TimeZoneId
|
||||||
|
|
||||||
/// The time zone information
|
|
||||||
TimeZone : TimeZone
|
|
||||||
|
|
||||||
/// The number of requests displayed per page
|
/// The number of requests displayed per page
|
||||||
PageSize : int
|
PageSize : int
|
||||||
|
|
||||||
/// How the as-of date should be automatically displayed
|
/// How the as-of date should be automatically displayed
|
||||||
AsOfDateDisplay : AsOfDateDisplay
|
AsOfDateDisplay : AsOfDateDisplay
|
||||||
}
|
}
|
||||||
with
|
|
||||||
|
/// Functions to support list preferences
|
||||||
|
module ListPreferences =
|
||||||
|
|
||||||
/// A set of preferences with their default values
|
/// A set of preferences with their default values
|
||||||
static member empty =
|
let empty =
|
||||||
{ SmallGroupId = SmallGroupId Guid.Empty
|
{ SmallGroupId = SmallGroupId Guid.Empty
|
||||||
DaysToExpire = 14
|
DaysToExpire = 14
|
||||||
DaysToKeepNew = 7
|
DaysToKeepNew = 7
|
||||||
|
@ -507,61 +334,14 @@ with
|
||||||
DefaultEmailType = HtmlFormat
|
DefaultEmailType = HtmlFormat
|
||||||
IsPublic = false
|
IsPublic = false
|
||||||
TimeZoneId = TimeZoneId "America/Denver"
|
TimeZoneId = TimeZoneId "America/Denver"
|
||||||
TimeZone = TimeZone.empty
|
|
||||||
PageSize = 100
|
PageSize = 100
|
||||||
AsOfDateDisplay = NoDisplay
|
AsOfDateDisplay = NoDisplay
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configure EF for this entity
|
|
||||||
static member internal ConfigureEF (mb : ModelBuilder) =
|
|
||||||
mb.Entity<ListPreferences> (fun it ->
|
|
||||||
seq<obj> {
|
|
||||||
it.ToTable "list_preference"
|
|
||||||
it.HasKey (fun lp -> lp.SmallGroupId :> obj)
|
|
||||||
it.Property(fun lp -> lp.SmallGroupId).HasColumnName "small_group_id"
|
|
||||||
it.Property(fun lp -> lp.DaysToKeepNew).HasColumnName("days_to_keep_new").IsRequired().HasDefaultValue 7
|
|
||||||
it.Property(fun lp -> lp.DaysToExpire).HasColumnName("days_to_expire").IsRequired().HasDefaultValue 14
|
|
||||||
it.Property(fun lp -> lp.LongTermUpdateWeeks).HasColumnName("long_term_update_weeks").IsRequired()
|
|
||||||
.HasDefaultValue 4
|
|
||||||
it.Property(fun lp -> lp.EmailFromName).HasColumnName("email_from_name").IsRequired()
|
|
||||||
.HasDefaultValue "PrayerTracker"
|
|
||||||
it.Property(fun lp -> lp.EmailFromAddress).HasColumnName("email_from_address").IsRequired()
|
|
||||||
.HasDefaultValue "prayer@djs-consulting.com"
|
|
||||||
it.Property(fun lp -> lp.Fonts).HasColumnName("fonts").IsRequired()
|
|
||||||
.HasDefaultValue "Century Gothic,Tahoma,Luxi Sans,sans-serif"
|
|
||||||
it.Property(fun lp -> lp.HeadingColor).HasColumnName("heading_color").IsRequired()
|
|
||||||
.HasDefaultValue "maroon"
|
|
||||||
it.Property(fun lp -> lp.LineColor).HasColumnName("line_color").IsRequired().HasDefaultValue "navy"
|
|
||||||
it.Property(fun lp -> lp.HeadingFontSize).HasColumnName("heading_font_size").IsRequired()
|
|
||||||
.HasDefaultValue 16
|
|
||||||
it.Property(fun lp -> lp.TextFontSize).HasColumnName("text_font_size").IsRequired().HasDefaultValue 12
|
|
||||||
it.Property(fun lp -> lp.RequestSort).HasColumnName("request_sort").IsRequired().HasMaxLength(1)
|
|
||||||
.HasDefaultValue SortByDate
|
|
||||||
it.Property(fun lp -> lp.GroupPassword).HasColumnName("group_password").IsRequired().HasDefaultValue ""
|
|
||||||
it.Property(fun lp -> lp.DefaultEmailType).HasColumnName("default_email_type").IsRequired()
|
|
||||||
.HasDefaultValue HtmlFormat
|
|
||||||
it.Property(fun lp -> lp.IsPublic).HasColumnName("is_public").IsRequired().HasDefaultValue false
|
|
||||||
it.Property(fun lp -> lp.TimeZoneId).HasColumnName("time_zone_id").IsRequired()
|
|
||||||
.HasDefaultValue (TimeZoneId "America/Denver")
|
|
||||||
it.Property(fun lp -> lp.PageSize).HasColumnName("page_size").IsRequired().HasDefaultValue 100
|
|
||||||
it.Property(fun lp -> lp.AsOfDateDisplay).HasColumnName("as_of_date_display").IsRequired()
|
|
||||||
.HasMaxLength(1).HasDefaultValue NoDisplay
|
|
||||||
} |> List.ofSeq |> ignore)
|
|
||||||
|> ignore
|
|
||||||
mb.Model.FindEntityType(typeof<ListPreferences>).FindProperty(nameof ListPreferences.empty.SmallGroupId)
|
|
||||||
.SetValueConverter (Converters.SmallGroupIdConverter ())
|
|
||||||
mb.Model.FindEntityType(typeof<ListPreferences>).FindProperty(nameof ListPreferences.empty.RequestSort)
|
|
||||||
.SetValueConverter (Converters.RequestSortConverter ())
|
|
||||||
mb.Model.FindEntityType(typeof<ListPreferences>).FindProperty(nameof ListPreferences.empty.DefaultEmailType)
|
|
||||||
.SetValueConverter (Converters.EmailFormatConverter ())
|
|
||||||
mb.Model.FindEntityType(typeof<ListPreferences>).FindProperty(nameof ListPreferences.empty.TimeZoneId)
|
|
||||||
.SetValueConverter (Converters.TimeZoneIdConverter ())
|
|
||||||
mb.Model.FindEntityType(typeof<ListPreferences>).FindProperty(nameof ListPreferences.empty.AsOfDateDisplay)
|
|
||||||
.SetValueConverter (Converters.AsOfDateDisplayConverter ())
|
|
||||||
|
|
||||||
|
|
||||||
/// A member of a small group
|
/// A member of a small group
|
||||||
and [<CLIMutable; NoComparison; NoEquality>] Member =
|
[<NoComparison; NoEquality>]
|
||||||
|
type Member =
|
||||||
{ /// The ID of the small group member
|
{ /// The ID of the small group member
|
||||||
Id : MemberId
|
Id : MemberId
|
||||||
|
|
||||||
|
@ -576,44 +356,24 @@ and [<CLIMutable; NoComparison; NoEquality>] Member =
|
||||||
|
|
||||||
/// The type of e-mail preferred by this member
|
/// The type of e-mail preferred by this member
|
||||||
Format : EmailFormat option
|
Format : EmailFormat option
|
||||||
|
|
||||||
/// The small group to which this member belongs
|
|
||||||
SmallGroup : SmallGroup
|
|
||||||
}
|
}
|
||||||
with
|
|
||||||
|
/// Functions to support small group members
|
||||||
|
module Member =
|
||||||
|
|
||||||
/// An empty member
|
/// An empty member
|
||||||
static member empty =
|
let empty =
|
||||||
{ Id = MemberId Guid.Empty
|
{ Id = MemberId Guid.Empty
|
||||||
SmallGroupId = SmallGroupId Guid.Empty
|
SmallGroupId = SmallGroupId Guid.Empty
|
||||||
Name = ""
|
Name = ""
|
||||||
Email = ""
|
Email = ""
|
||||||
Format = None
|
Format = None
|
||||||
SmallGroup = SmallGroup.empty
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configure EF for this entity
|
|
||||||
static member internal ConfigureEF (mb : ModelBuilder) =
|
|
||||||
mb.Entity<Member> (fun it ->
|
|
||||||
seq<obj> {
|
|
||||||
it.ToTable "member"
|
|
||||||
it.Property(fun m -> m.Id).HasColumnName "id"
|
|
||||||
it.Property(fun m -> m.SmallGroupId).HasColumnName("small_group_id").IsRequired ()
|
|
||||||
it.Property(fun m -> m.Name).HasColumnName("member_name").IsRequired ()
|
|
||||||
it.Property(fun m -> m.Email).HasColumnName("email").IsRequired ()
|
|
||||||
it.Property(fun m -> m.Format).HasColumnName "email_format"
|
|
||||||
} |> List.ofSeq |> ignore)
|
|
||||||
|> ignore
|
|
||||||
mb.Model.FindEntityType(typeof<Member>).FindProperty(nameof Member.empty.Id)
|
|
||||||
.SetValueConverter (Converters.MemberIdConverter ())
|
|
||||||
mb.Model.FindEntityType(typeof<Member>).FindProperty(nameof Member.empty.SmallGroupId)
|
|
||||||
.SetValueConverter (Converters.SmallGroupIdConverter ())
|
|
||||||
mb.Model.FindEntityType(typeof<Member>).FindProperty(nameof Member.empty.Format)
|
|
||||||
.SetValueConverter (Converters.EmailFormatOptionConverter ())
|
|
||||||
|
|
||||||
|
|
||||||
/// This represents a single prayer request
|
/// This represents a single prayer request
|
||||||
and [<CLIMutable; NoComparison; NoEquality>] PrayerRequest =
|
[<NoComparison; NoEquality>]
|
||||||
|
type PrayerRequest =
|
||||||
{ /// The ID of this request
|
{ /// The ID of this request
|
||||||
Id : PrayerRequestId
|
Id : PrayerRequestId
|
||||||
|
|
||||||
|
@ -641,66 +401,15 @@ and [<CLIMutable; NoComparison; NoEquality>] PrayerRequest =
|
||||||
/// Whether the chaplain should be notified for this request
|
/// Whether the chaplain should be notified for this request
|
||||||
NotifyChaplain : bool
|
NotifyChaplain : bool
|
||||||
|
|
||||||
/// The user who entered this request
|
|
||||||
User : User
|
|
||||||
|
|
||||||
/// The small group to which this request belongs
|
|
||||||
SmallGroup : SmallGroup
|
|
||||||
|
|
||||||
/// Is this request expired?
|
/// Is this request expired?
|
||||||
Expiration : Expiration
|
Expiration : Expiration
|
||||||
}
|
}
|
||||||
with
|
// functions are below small group functions
|
||||||
|
|
||||||
/// An empty request
|
|
||||||
static member empty =
|
|
||||||
{ Id = PrayerRequestId Guid.Empty
|
|
||||||
RequestType = CurrentRequest
|
|
||||||
UserId = UserId Guid.Empty
|
|
||||||
SmallGroupId = SmallGroupId Guid.Empty
|
|
||||||
EnteredDate = Instant.MinValue
|
|
||||||
UpdatedDate = Instant.MinValue
|
|
||||||
Requestor = None
|
|
||||||
Text = ""
|
|
||||||
NotifyChaplain = false
|
|
||||||
User = User.empty
|
|
||||||
SmallGroup = SmallGroup.empty
|
|
||||||
Expiration = Automatic
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configure EF for this entity
|
|
||||||
static member internal ConfigureEF (mb : ModelBuilder) =
|
|
||||||
mb.Entity<PrayerRequest> (fun it ->
|
|
||||||
seq<obj> {
|
|
||||||
it.ToTable "prayer_request"
|
|
||||||
it.Property(fun pr -> pr.Id).HasColumnName "id"
|
|
||||||
it.Property(fun pr -> pr.RequestType).HasColumnName("request_type").IsRequired ()
|
|
||||||
it.Property(fun pr -> pr.UserId).HasColumnName "user_id"
|
|
||||||
it.Property(fun pr -> pr.SmallGroupId).HasColumnName "small_group_id"
|
|
||||||
it.Property(fun pr -> pr.EnteredDate).HasColumnName "entered_date"
|
|
||||||
it.Property(fun pr -> pr.UpdatedDate).HasColumnName "updated_date"
|
|
||||||
it.Property(fun pr -> pr.Requestor).HasColumnName "requestor"
|
|
||||||
it.Property(fun pr -> pr.Text).HasColumnName("request_text").IsRequired ()
|
|
||||||
it.Property(fun pr -> pr.NotifyChaplain).HasColumnName "notify_chaplain"
|
|
||||||
it.Property(fun pr -> pr.Expiration).HasColumnName "expiration"
|
|
||||||
} |> List.ofSeq |> ignore)
|
|
||||||
|> ignore
|
|
||||||
mb.Model.FindEntityType(typeof<PrayerRequest>).FindProperty(nameof PrayerRequest.empty.Id)
|
|
||||||
.SetValueConverter (Converters.PrayerRequestIdConverter ())
|
|
||||||
mb.Model.FindEntityType(typeof<PrayerRequest>).FindProperty(nameof PrayerRequest.empty.RequestType)
|
|
||||||
.SetValueConverter (Converters.PrayerRequestTypeConverter ())
|
|
||||||
mb.Model.FindEntityType(typeof<PrayerRequest>).FindProperty(nameof PrayerRequest.empty.UserId)
|
|
||||||
.SetValueConverter (Converters.UserIdConverter ())
|
|
||||||
mb.Model.FindEntityType(typeof<PrayerRequest>).FindProperty(nameof PrayerRequest.empty.SmallGroupId)
|
|
||||||
.SetValueConverter (Converters.SmallGroupIdConverter ())
|
|
||||||
mb.Model.FindEntityType(typeof<PrayerRequest>).FindProperty(nameof PrayerRequest.empty.Requestor)
|
|
||||||
.SetValueConverter (OptionConverter<string> ())
|
|
||||||
mb.Model.FindEntityType(typeof<PrayerRequest>).FindProperty(nameof PrayerRequest.empty.Expiration)
|
|
||||||
.SetValueConverter (Converters.ExpirationConverter ())
|
|
||||||
|
|
||||||
|
|
||||||
/// This represents a small group (Sunday School class, Bible study group, etc.)
|
/// This represents a small group (Sunday School class, Bible study group, etc.)
|
||||||
and [<CLIMutable; NoComparison; NoEquality>] SmallGroup =
|
[<NoComparison; NoEquality>]
|
||||||
|
type SmallGroup =
|
||||||
{ /// The ID of this small group
|
{ /// The ID of this small group
|
||||||
Id : SmallGroupId
|
Id : SmallGroupId
|
||||||
|
|
||||||
|
@ -710,215 +419,21 @@ and [<CLIMutable; NoComparison; NoEquality>] SmallGroup =
|
||||||
/// The name of the group
|
/// The name of the group
|
||||||
Name : string
|
Name : string
|
||||||
|
|
||||||
/// The church to which this small group belongs
|
|
||||||
Church : Church
|
|
||||||
|
|
||||||
/// The preferences for the request list
|
/// The preferences for the request list
|
||||||
Preferences : ListPreferences
|
Preferences : ListPreferences
|
||||||
|
|
||||||
/// The members of the group
|
|
||||||
Members : ResizeArray<Member>
|
|
||||||
|
|
||||||
/// Prayer requests for this small group
|
|
||||||
PrayerRequests : ResizeArray<PrayerRequest>
|
|
||||||
|
|
||||||
/// The users authorized to manage this group
|
|
||||||
Users : ResizeArray<UserSmallGroup>
|
|
||||||
}
|
}
|
||||||
with
|
|
||||||
|
/// Functions to support small groups
|
||||||
|
module SmallGroup =
|
||||||
|
|
||||||
/// An empty small group
|
/// An empty small group
|
||||||
static member empty =
|
let empty =
|
||||||
{ Id = SmallGroupId Guid.Empty
|
{ Id = SmallGroupId Guid.Empty
|
||||||
ChurchId = ChurchId Guid.Empty
|
ChurchId = ChurchId Guid.Empty
|
||||||
Name = ""
|
Name = ""
|
||||||
Church = Church.empty
|
|
||||||
Preferences = ListPreferences.empty
|
Preferences = ListPreferences.empty
|
||||||
Members = ResizeArray<Member> ()
|
|
||||||
PrayerRequests = ResizeArray<PrayerRequest> ()
|
|
||||||
Users = ResizeArray<UserSmallGroup> ()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configure EF for this entity
|
|
||||||
static member internal ConfigureEF (mb : ModelBuilder) =
|
|
||||||
mb.Entity<SmallGroup> (fun it ->
|
|
||||||
seq<obj> {
|
|
||||||
it.ToTable "small_group"
|
|
||||||
it.Property(fun sg -> sg.Id).HasColumnName "id"
|
|
||||||
it.Property(fun sg -> sg.ChurchId).HasColumnName "church_id"
|
|
||||||
it.Property(fun sg -> sg.Name).HasColumnName("group_name").IsRequired ()
|
|
||||||
it.HasOne(fun sg -> sg.Preferences)
|
|
||||||
.WithOne()
|
|
||||||
.HasPrincipalKey(fun sg -> sg.Id :> obj)
|
|
||||||
.HasForeignKey(fun (lp : ListPreferences) -> lp.SmallGroupId :> obj)
|
|
||||||
} |> List.ofSeq |> ignore)
|
|
||||||
|> ignore
|
|
||||||
mb.Model.FindEntityType(typeof<SmallGroup>).FindProperty(nameof SmallGroup.empty.Id)
|
|
||||||
.SetValueConverter (Converters.SmallGroupIdConverter ())
|
|
||||||
mb.Model.FindEntityType(typeof<SmallGroup>).FindProperty(nameof SmallGroup.empty.ChurchId)
|
|
||||||
.SetValueConverter (Converters.ChurchIdConverter ())
|
|
||||||
|
|
||||||
|
|
||||||
/// This represents a time zone in which a class may reside
|
|
||||||
and [<CLIMutable; NoComparison; NoEquality>] TimeZone =
|
|
||||||
{ /// The Id for this time zone (uses tzdata names)
|
|
||||||
Id : TimeZoneId
|
|
||||||
|
|
||||||
/// The description of this time zone
|
|
||||||
Description : string
|
|
||||||
|
|
||||||
/// The order in which this timezone should be displayed
|
|
||||||
SortOrder : int
|
|
||||||
|
|
||||||
/// Whether this timezone is active
|
|
||||||
IsActive : bool
|
|
||||||
}
|
|
||||||
with
|
|
||||||
|
|
||||||
/// An empty time zone
|
|
||||||
static member empty =
|
|
||||||
{ Id = TimeZoneId ""
|
|
||||||
Description = ""
|
|
||||||
SortOrder = 0
|
|
||||||
IsActive = false
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configure EF for this entity
|
|
||||||
static member internal ConfigureEF (mb : ModelBuilder) =
|
|
||||||
mb.Entity<TimeZone> (fun it ->
|
|
||||||
seq<obj> {
|
|
||||||
it.ToTable "time_zone"
|
|
||||||
it.Property(fun tz -> tz.Id).HasColumnName "id"
|
|
||||||
it.Property(fun tz -> tz.Description).HasColumnName("description").IsRequired ()
|
|
||||||
it.Property(fun tz -> tz.SortOrder).HasColumnName "sort_order"
|
|
||||||
it.Property(fun tz -> tz.IsActive).HasColumnName "is_active"
|
|
||||||
} |> List.ofSeq |> ignore)
|
|
||||||
|> ignore
|
|
||||||
mb.Model.FindEntityType(typeof<TimeZone>).FindProperty(nameof TimeZone.empty.Id)
|
|
||||||
.SetValueConverter (Converters.TimeZoneIdConverter ())
|
|
||||||
|
|
||||||
|
|
||||||
/// This represents a user of PrayerTracker
|
|
||||||
and [<CLIMutable; NoComparison; NoEquality>] User =
|
|
||||||
{ /// The ID of this user
|
|
||||||
Id : UserId
|
|
||||||
|
|
||||||
/// The first name of this user
|
|
||||||
FirstName : string
|
|
||||||
|
|
||||||
/// The last name of this user
|
|
||||||
LastName : string
|
|
||||||
|
|
||||||
/// The e-mail address of the user
|
|
||||||
Email : string
|
|
||||||
|
|
||||||
/// Whether this user is a PrayerTracker system administrator
|
|
||||||
IsAdmin : bool
|
|
||||||
|
|
||||||
/// The user's hashed password
|
|
||||||
PasswordHash : string
|
|
||||||
|
|
||||||
/// The salt for the user's hashed password
|
|
||||||
Salt : Guid option
|
|
||||||
|
|
||||||
/// The last time the user was seen (set whenever the user is loaded into a session)
|
|
||||||
LastSeen : Instant option
|
|
||||||
|
|
||||||
/// The small groups which this user is authorized
|
|
||||||
SmallGroups : ResizeArray<UserSmallGroup>
|
|
||||||
}
|
|
||||||
with
|
|
||||||
|
|
||||||
/// An empty user
|
|
||||||
static member empty =
|
|
||||||
{ Id = UserId Guid.Empty
|
|
||||||
FirstName = ""
|
|
||||||
LastName = ""
|
|
||||||
Email = ""
|
|
||||||
IsAdmin = false
|
|
||||||
PasswordHash = ""
|
|
||||||
Salt = None
|
|
||||||
LastSeen = None
|
|
||||||
SmallGroups = ResizeArray<UserSmallGroup> ()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The full name of the user
|
|
||||||
member this.Name =
|
|
||||||
$"{this.FirstName} {this.LastName}"
|
|
||||||
|
|
||||||
/// Configure EF for this entity
|
|
||||||
static member internal ConfigureEF (mb : ModelBuilder) =
|
|
||||||
mb.Entity<User> (fun it ->
|
|
||||||
seq<obj> {
|
|
||||||
it.ToTable "pt_user"
|
|
||||||
it.Ignore(fun u -> u.Name :> obj)
|
|
||||||
it.Property(fun u -> u.Id).HasColumnName "id"
|
|
||||||
it.Property(fun u -> u.FirstName).HasColumnName("first_name").IsRequired ()
|
|
||||||
it.Property(fun u -> u.LastName).HasColumnName("last_name").IsRequired ()
|
|
||||||
it.Property(fun u -> u.Email).HasColumnName("email").IsRequired ()
|
|
||||||
it.Property(fun u -> u.IsAdmin).HasColumnName "is_admin"
|
|
||||||
it.Property(fun u -> u.PasswordHash).HasColumnName("password_hash").IsRequired ()
|
|
||||||
it.Property(fun u -> u.Salt).HasColumnName "salt"
|
|
||||||
it.Property(fun u -> u.LastSeen).HasColumnName "last_seen"
|
|
||||||
} |> List.ofSeq |> ignore)
|
|
||||||
|> ignore
|
|
||||||
mb.Model.FindEntityType(typeof<User>).FindProperty(nameof User.empty.Id)
|
|
||||||
.SetValueConverter (Converters.UserIdConverter ())
|
|
||||||
mb.Model.FindEntityType(typeof<User>).FindProperty(nameof User.empty.Salt)
|
|
||||||
.SetValueConverter (OptionConverter<Guid> ())
|
|
||||||
mb.Model.FindEntityType(typeof<User>).FindProperty(nameof User.empty.LastSeen)
|
|
||||||
.SetValueConverter (OptionConverter<Instant> ())
|
|
||||||
|
|
||||||
|
|
||||||
/// Cross-reference between user and small group
|
|
||||||
and [<CLIMutable; NoComparison; NoEquality>] UserSmallGroup =
|
|
||||||
{ /// The Id of the user who has access to the small group
|
|
||||||
UserId : UserId
|
|
||||||
|
|
||||||
/// The Id of the small group to which the user has access
|
|
||||||
SmallGroupId : SmallGroupId
|
|
||||||
|
|
||||||
/// The user who has access to the small group
|
|
||||||
User : User
|
|
||||||
|
|
||||||
/// The small group to which the user has access
|
|
||||||
SmallGroup : SmallGroup
|
|
||||||
}
|
|
||||||
with
|
|
||||||
|
|
||||||
/// An empty user/small group xref
|
|
||||||
static member empty =
|
|
||||||
{ UserId = UserId Guid.Empty
|
|
||||||
SmallGroupId = SmallGroupId Guid.Empty
|
|
||||||
User = User.empty
|
|
||||||
SmallGroup = SmallGroup.empty
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configure EF for this entity
|
|
||||||
static member internal ConfigureEF (mb : ModelBuilder) =
|
|
||||||
mb.Entity<UserSmallGroup> (fun it ->
|
|
||||||
seq<obj> {
|
|
||||||
it.ToTable "user_small_group"
|
|
||||||
it.HasKey (nameof UserSmallGroup.empty.UserId, nameof UserSmallGroup.empty.SmallGroupId)
|
|
||||||
it.Property(fun usg -> usg.UserId).HasColumnName "user_id"
|
|
||||||
it.Property(fun usg -> usg.SmallGroupId).HasColumnName "small_group_id"
|
|
||||||
it.HasOne(fun usg -> usg.User)
|
|
||||||
.WithMany(fun u -> u.SmallGroups :> seq<UserSmallGroup>)
|
|
||||||
.HasForeignKey(fun usg -> usg.UserId :> obj)
|
|
||||||
it.HasOne(fun usg -> usg.SmallGroup)
|
|
||||||
.WithMany(fun sg -> sg.Users :> seq<UserSmallGroup>)
|
|
||||||
.HasForeignKey(fun usg -> usg.SmallGroupId :> obj)
|
|
||||||
} |> List.ofSeq |> ignore)
|
|
||||||
|> ignore
|
|
||||||
mb.Model.FindEntityType(typeof<UserSmallGroup>).FindProperty(nameof UserSmallGroup.empty.UserId)
|
|
||||||
.SetValueConverter (Converters.UserIdConverter ())
|
|
||||||
mb.Model.FindEntityType(typeof<UserSmallGroup>).FindProperty(nameof UserSmallGroup.empty.SmallGroupId)
|
|
||||||
.SetValueConverter (Converters.SmallGroupIdConverter ())
|
|
||||||
|
|
||||||
|
|
||||||
/// Support functions for small groups
|
|
||||||
module SmallGroup =
|
|
||||||
|
|
||||||
/// The DateTimeZone for the time zone ID for this small group
|
/// The DateTimeZone for the time zone ID for this small group
|
||||||
let timeZone group =
|
let timeZone group =
|
||||||
let tzId = TimeZoneId.toString group.Preferences.TimeZoneId
|
let tzId = TimeZoneId.toString group.Preferences.TimeZoneId
|
||||||
|
@ -935,10 +450,23 @@ module SmallGroup =
|
||||||
(localTimeNow clock group).Date
|
(localTimeNow clock group).Date
|
||||||
|
|
||||||
|
|
||||||
|
/// Functions to support prayer requests
|
||||||
/// Support functions for prayer requests
|
|
||||||
module PrayerRequest =
|
module PrayerRequest =
|
||||||
|
|
||||||
|
/// An empty request
|
||||||
|
let empty =
|
||||||
|
{ Id = PrayerRequestId Guid.Empty
|
||||||
|
RequestType = CurrentRequest
|
||||||
|
UserId = UserId Guid.Empty
|
||||||
|
SmallGroupId = SmallGroupId Guid.Empty
|
||||||
|
EnteredDate = Instant.MinValue
|
||||||
|
UpdatedDate = Instant.MinValue
|
||||||
|
Requestor = None
|
||||||
|
Text = ""
|
||||||
|
NotifyChaplain = false
|
||||||
|
Expiration = Automatic
|
||||||
|
}
|
||||||
|
|
||||||
/// Is this request expired?
|
/// Is this request expired?
|
||||||
let isExpired (asOf : LocalDate) group req =
|
let isExpired (asOf : LocalDate) group req =
|
||||||
match req.Expiration, req.RequestType with
|
match req.Expiration, req.RequestType with
|
||||||
|
@ -958,21 +486,65 @@ module PrayerRequest =
|
||||||
>= req.UpdatedDate.InZone(SmallGroup.timeZone group).Date
|
>= req.UpdatedDate.InZone(SmallGroup.timeZone group).Date
|
||||||
|
|
||||||
|
|
||||||
/// Information needed to display the public/protected request list and small group maintenance pages
|
/// This represents a user of PrayerTracker
|
||||||
[<NoComparison; NoEquality>]
|
[<NoComparison; NoEquality>]
|
||||||
type SmallGroupInfo =
|
type User =
|
||||||
{ /// The ID of the small group
|
{ /// The ID of this user
|
||||||
Id : string
|
Id : UserId
|
||||||
|
|
||||||
/// The name of the small group
|
/// The first name of this user
|
||||||
Name : string
|
FirstName : string
|
||||||
|
|
||||||
/// The name of the church to which the small group belongs
|
/// The last name of this user
|
||||||
ChurchName : string
|
LastName : string
|
||||||
|
|
||||||
/// The ID of the time zone for the small group
|
/// The e-mail address of the user
|
||||||
TimeZoneId : TimeZoneId
|
Email : string
|
||||||
|
|
||||||
/// Whether the small group has a publicly-available request list
|
/// Whether this user is a PrayerTracker system administrator
|
||||||
IsPublic : bool
|
IsAdmin : bool
|
||||||
|
|
||||||
|
/// The user's hashed password
|
||||||
|
PasswordHash : string
|
||||||
|
|
||||||
|
/// The last time the user was seen (set whenever the user is loaded into a session)
|
||||||
|
LastSeen : Instant option
|
||||||
|
}
|
||||||
|
with
|
||||||
|
/// The full name of the user
|
||||||
|
member this.Name =
|
||||||
|
$"{this.FirstName} {this.LastName}"
|
||||||
|
|
||||||
|
/// Functions to support users
|
||||||
|
module User =
|
||||||
|
|
||||||
|
/// An empty user
|
||||||
|
let empty =
|
||||||
|
{ Id = UserId Guid.Empty
|
||||||
|
FirstName = ""
|
||||||
|
LastName = ""
|
||||||
|
Email = ""
|
||||||
|
IsAdmin = false
|
||||||
|
PasswordHash = ""
|
||||||
|
LastSeen = None
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Cross-reference between user and small group
|
||||||
|
[<NoComparison; NoEquality>]
|
||||||
|
type UserSmallGroup =
|
||||||
|
{ /// The Id of the user who has access to the small group
|
||||||
|
UserId : UserId
|
||||||
|
|
||||||
|
/// The Id of the small group to which the user has access
|
||||||
|
SmallGroupId : SmallGroupId
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Functions to support user/small group cross-reference
|
||||||
|
module UserSmallGroup =
|
||||||
|
|
||||||
|
/// An empty user/small group xref
|
||||||
|
let empty =
|
||||||
|
{ UserId = UserId Guid.Empty
|
||||||
|
SmallGroupId = SmallGroupId Guid.Empty
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,391 +0,0 @@
|
||||||
namespace PrayerTracker.Migrations
|
|
||||||
|
|
||||||
open System
|
|
||||||
open Microsoft.EntityFrameworkCore
|
|
||||||
open Microsoft.EntityFrameworkCore.Infrastructure
|
|
||||||
open Microsoft.EntityFrameworkCore.Migrations
|
|
||||||
open Npgsql.EntityFrameworkCore.PostgreSQL.Metadata
|
|
||||||
open PrayerTracker
|
|
||||||
open PrayerTracker.Entities
|
|
||||||
|
|
||||||
[<DbContext (typeof<AppDbContext>)>]
|
|
||||||
[<Migration "20161217153124_InitialDatabase">]
|
|
||||||
type InitialDatabase () =
|
|
||||||
inherit Migration ()
|
|
||||||
override _.Up (migrationBuilder : MigrationBuilder) =
|
|
||||||
migrationBuilder.EnsureSchema (name = "pt")
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable (
|
|
||||||
name = "church",
|
|
||||||
schema = "pt",
|
|
||||||
columns = (fun table ->
|
|
||||||
{| Id = table.Column<Guid> (name = "id", nullable = false)
|
|
||||||
City = table.Column<string> (name = "city", nullable = false)
|
|
||||||
HasVpsInterface = table.Column<bool> (name = "has_vps_interface", nullable = false)
|
|
||||||
InterfaceAddress = table.Column<string> (name = "interface_address", nullable = true)
|
|
||||||
Name = table.Column<string> (name = "church_Name", nullable = false)
|
|
||||||
State = table.Column<string> (name = "state", nullable = false, maxLength = Nullable<int> 2)
|
|
||||||
|}),
|
|
||||||
constraints = fun table ->
|
|
||||||
table.PrimaryKey ("pk_church", fun x -> upcast x.Id) |> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable (
|
|
||||||
name = "time_zone",
|
|
||||||
schema = "pt",
|
|
||||||
columns = (fun table ->
|
|
||||||
{| Id = table.Column<string> (name = "id", nullable = false)
|
|
||||||
Description = table.Column<string> (name = "description", nullable = false)
|
|
||||||
IsActive = table.Column<bool> (name = "is_active", nullable = false)
|
|
||||||
SortOrder = table.Column<int> (name = "sort_order", nullable = false)
|
|
||||||
|}),
|
|
||||||
constraints = fun table ->
|
|
||||||
table.PrimaryKey ("pk_time_zone", fun x -> upcast x.Id) |> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable (
|
|
||||||
name = "pt_user",
|
|
||||||
schema = "pt",
|
|
||||||
columns = (fun table ->
|
|
||||||
{| Id = table.Column<Guid> (name = "id", nullable = false)
|
|
||||||
Email = table.Column<string> (name = "email", nullable = false)
|
|
||||||
FirstName = table.Column<string> (name = "first_name", nullable = false)
|
|
||||||
IsAdmin = table.Column<bool> (name = "is_admin", nullable = false)
|
|
||||||
LastName = table.Column<string> (name = "last_name", nullable = false)
|
|
||||||
PasswordHash = table.Column<string> (name = "password_hash", nullable = false)
|
|
||||||
Salt = table.Column<Guid> (name = "salt", nullable = true)
|
|
||||||
LastSeen = table.Column<DateTime> (name = "last_seen", nullable = true)
|
|
||||||
|}),
|
|
||||||
constraints = fun table ->
|
|
||||||
table.PrimaryKey("pk_pt_user", fun x -> upcast x.Id) |> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable (
|
|
||||||
name = "small_group",
|
|
||||||
schema = "pt",
|
|
||||||
columns = (fun table ->
|
|
||||||
{| Id = table.Column<Guid> (name = "id", nullable = false)
|
|
||||||
ChurchId = table.Column<Guid> (name = "church_id", nullable = false)
|
|
||||||
Name = table.Column<string> (name = "group_name", nullable = false)
|
|
||||||
|}),
|
|
||||||
constraints = fun table ->
|
|
||||||
table.PrimaryKey ("pk_small_group", fun x -> upcast x.Id) |> ignore
|
|
||||||
table.ForeignKey (
|
|
||||||
name = "fk_small_group_church_id",
|
|
||||||
column = (fun x -> upcast x.ChurchId),
|
|
||||||
principalSchema = "pt",
|
|
||||||
principalTable = "church",
|
|
||||||
principalColumn = "id",
|
|
||||||
onDelete = ReferentialAction.Cascade)
|
|
||||||
|> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable (
|
|
||||||
name = "list_preference",
|
|
||||||
schema = "pt",
|
|
||||||
columns = (fun table ->
|
|
||||||
{| SmallGroupId = table.Column<Guid> (name = "small_group_id", nullable = false)
|
|
||||||
AsOfDateDisplay = table.Column<string> (name = "as_of_date_display", nullable = false, defaultValue = "N", maxLength = Nullable<int> 1)
|
|
||||||
DaysToExpire = table.Column<int> (name = "days_to_expire", nullable = false, defaultValue = 14)
|
|
||||||
DaysToKeepNew = table.Column<int> (name = "days_to_keep_new", nullable = false, defaultValue = 7)
|
|
||||||
DefaultEmailType = table.Column<string> (name = "default_email_type", nullable = false, defaultValue = "Html")
|
|
||||||
EmailFromAddress = table.Column<string> (name = "email_from_address", nullable = false, defaultValue = "prayer@djs-consulting.com")
|
|
||||||
EmailFromName = table.Column<string> (name = "email_from_name", nullable = false, defaultValue = "PrayerTracker")
|
|
||||||
Fonts = table.Column<string> (name = "fonts", nullable = false, defaultValue = "Century Gothic,Tahoma,Luxi Sans,sans-serif")
|
|
||||||
GroupPassword = table.Column<string> (name = "group_password", nullable = false, defaultValue = "")
|
|
||||||
HeadingColor = table.Column<string> (name = "heading_color", nullable = false, defaultValue = "maroon")
|
|
||||||
HeadingFontSize = table.Column<int> (name = "heading_font_size", nullable = false, defaultValue = 16)
|
|
||||||
IsPublic = table.Column<bool> (name = "is_public", nullable = false, defaultValue = false)
|
|
||||||
LineColor = table.Column<string> (name = "line_color", nullable = false, defaultValue = "navy")
|
|
||||||
LongTermUpdateWeeks = table.Column<int> (name = "long_term_update_weeks", nullable = false, defaultValue = 4)
|
|
||||||
PageSize = table.Column<int> (name = "page_size", nullable = false, defaultValue = 100)
|
|
||||||
RequestSort = table.Column<string> (name = "request_sort", nullable = false, defaultValue = "D", maxLength = Nullable<int> 1)
|
|
||||||
TextFontSize = table.Column<int> (name = "text_font_size", nullable = false, defaultValue = 12)
|
|
||||||
TimeZoneId = table.Column<string> (name = "time_zone_id", nullable = false, defaultValue = "America/Denver")
|
|
||||||
|}),
|
|
||||||
constraints = fun table ->
|
|
||||||
table.PrimaryKey ("pk_list_preference", fun x -> upcast x.SmallGroupId) |> ignore
|
|
||||||
table.ForeignKey (
|
|
||||||
name = "fk_list_preference_small_group_id",
|
|
||||||
column = (fun x -> upcast x.SmallGroupId),
|
|
||||||
principalSchema = "pt",
|
|
||||||
principalTable = "small_group",
|
|
||||||
principalColumn = "id",
|
|
||||||
onDelete = ReferentialAction.Cascade)
|
|
||||||
|> ignore
|
|
||||||
table.ForeignKey (
|
|
||||||
name = "fk_list_preference_time_zone_id",
|
|
||||||
column = (fun x -> upcast x.TimeZoneId),
|
|
||||||
principalSchema = "pt",
|
|
||||||
principalTable = "time_zone",
|
|
||||||
principalColumn = "id",
|
|
||||||
onDelete = ReferentialAction.Cascade)
|
|
||||||
|> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable (
|
|
||||||
name = "member",
|
|
||||||
schema = "pt",
|
|
||||||
columns = (fun table ->
|
|
||||||
{| Id = table.Column<Guid> (name = "id", nullable = false)
|
|
||||||
Email = table.Column<string> (name = "email", nullable = false)
|
|
||||||
Format = table.Column<string> (name = "email_format", nullable = true)
|
|
||||||
Name = table.Column<string> (name = "member_name", nullable = false)
|
|
||||||
SmallGroupId = table.Column<Guid> (name = "small_group_id", nullable = false)
|
|
||||||
|}),
|
|
||||||
constraints = fun table ->
|
|
||||||
table.PrimaryKey ("pk_member", fun x -> upcast x.Id) |> ignore
|
|
||||||
table.ForeignKey (
|
|
||||||
name = "fk_member_small_group_id",
|
|
||||||
column = (fun x -> upcast x.SmallGroupId),
|
|
||||||
principalSchema = "pt",
|
|
||||||
principalTable = "small_group",
|
|
||||||
principalColumn = "id",
|
|
||||||
onDelete = ReferentialAction.Cascade)
|
|
||||||
|> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable (
|
|
||||||
name = "prayer_request",
|
|
||||||
schema = "pt",
|
|
||||||
columns = (fun table ->
|
|
||||||
{| Id = table.Column<Guid> (name = "id", nullable = false)
|
|
||||||
Expiration = table.Column<bool> (name = "expiration", nullable = false)
|
|
||||||
EnteredDate = table.Column<DateTime> (name = "entered_date", nullable = false)
|
|
||||||
NotifyChaplain = table.Column<bool> (name = "notify_chaplain", nullable = false)
|
|
||||||
RequestType = table.Column<string> (name = "request_type", nullable = false)
|
|
||||||
Requestor = table.Column<string> (name = "requestor", nullable = true)
|
|
||||||
SmallGroupId = table.Column<Guid> (name = "small_group_id", nullable = false)
|
|
||||||
Text = table.Column<string> (name = "request_text", nullable = false)
|
|
||||||
UpdatedDate = table.Column<DateTime> (name = "updated_date", nullable = false)
|
|
||||||
UserId = table.Column<Guid> (name = "user_id", nullable = false)
|
|
||||||
|}),
|
|
||||||
constraints = fun table ->
|
|
||||||
table.PrimaryKey ("pk_prayer_request", fun x -> upcast x.Id) |> ignore
|
|
||||||
table.ForeignKey (
|
|
||||||
name = "fk_prayer_request_small_group_id",
|
|
||||||
column = (fun x -> upcast x.SmallGroupId),
|
|
||||||
principalSchema = "pt",
|
|
||||||
principalTable = "small_group",
|
|
||||||
principalColumn = "i",
|
|
||||||
onDelete = ReferentialAction.Cascade)
|
|
||||||
|> ignore
|
|
||||||
table.ForeignKey (
|
|
||||||
name = "fk_prayer_request_user_id",
|
|
||||||
column = (fun x -> upcast x.UserId),
|
|
||||||
principalSchema = "pt",
|
|
||||||
principalTable = "pt_user",
|
|
||||||
principalColumn = "id",
|
|
||||||
onDelete = ReferentialAction.Cascade)
|
|
||||||
|> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name = "user_small_group",
|
|
||||||
schema = "pt",
|
|
||||||
columns = (fun table ->
|
|
||||||
{| UserId = table.Column<Guid> (name = "user_id", nullable = false)
|
|
||||||
SmallGroupId = table.Column<Guid> (name = "small_group_id", nullable = false)
|
|
||||||
|}),
|
|
||||||
constraints = fun table ->
|
|
||||||
table.PrimaryKey ("pk_user_small_group", fun x -> upcast x) |> ignore
|
|
||||||
table.ForeignKey (
|
|
||||||
name = "fk_user_small_group_small_group_id",
|
|
||||||
column = (fun x -> upcast x.SmallGroupId),
|
|
||||||
principalSchema = "pt",
|
|
||||||
principalTable = "small_group",
|
|
||||||
principalColumn = "id",
|
|
||||||
onDelete = ReferentialAction.Cascade)
|
|
||||||
|> ignore
|
|
||||||
table.ForeignKey (
|
|
||||||
name = "fk_user_small_group_user_id",
|
|
||||||
column = (fun x -> upcast x.UserId),
|
|
||||||
principalSchema = "pt",
|
|
||||||
principalTable = "pt_user",
|
|
||||||
principalColumn = "id",
|
|
||||||
onDelete = ReferentialAction.Cascade)
|
|
||||||
|> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex (name = "ix_list_preference_time_zone_id", schema = "pt", table = "list_preference", column = "time_zone_id") |> ignore
|
|
||||||
migrationBuilder.CreateIndex (name = "ix_member_small_group_id", schema = "pt", table = "member", column = "small_group_id") |> ignore
|
|
||||||
migrationBuilder.CreateIndex (name = "ix_prayer_request_small_group_id", schema = "pt", table = "prayer_request", column = "small_group_id") |> ignore
|
|
||||||
migrationBuilder.CreateIndex (name = "ix_prayer_request_user_id", schema = "pt", table = "prayer_request", column = "user_id") |> ignore
|
|
||||||
migrationBuilder.CreateIndex (name = "ix_small_group_church_id", schema = "pt", table = "small_group", column = "church_id") |> ignore
|
|
||||||
migrationBuilder.CreateIndex (name = "ix_user_small_group_small_group_id", schema = "pt", table = "user_small_group", column = "small_group_id") |> ignore
|
|
||||||
|
|
||||||
override _.Down (migrationBuilder : MigrationBuilder) =
|
|
||||||
migrationBuilder.DropTable (name = "list_preference", schema = "pt") |> ignore
|
|
||||||
migrationBuilder.DropTable (name = "member", schema = "pt") |> ignore
|
|
||||||
migrationBuilder.DropTable (name = "prayer_request", schema = "pt") |> ignore
|
|
||||||
migrationBuilder.DropTable (name = "user_small_group", schema = "pt") |> ignore
|
|
||||||
migrationBuilder.DropTable (name = "time_zone", schema = "pt") |> ignore
|
|
||||||
migrationBuilder.DropTable (name = "small_group", schema = "pt") |> ignore
|
|
||||||
migrationBuilder.DropTable (name = "pt_user", schema = "pt") |> ignore
|
|
||||||
migrationBuilder.DropTable (name = "church", schema = "pt") |> ignore
|
|
||||||
|
|
||||||
|
|
||||||
override _.BuildTargetModel (modelBuilder : ModelBuilder) =
|
|
||||||
modelBuilder
|
|
||||||
.HasDefaultSchema("pt")
|
|
||||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn)
|
|
||||||
.HasAnnotation("ProductVersion", "1.1.0-rtm-22752")
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
modelBuilder.Entity (typeof<Church>, fun b ->
|
|
||||||
b.Property<Guid>("Id").HasColumnName("id").ValueGeneratedOnAdd() |> ignore
|
|
||||||
b.Property<string>("City").HasColumnName("city").IsRequired() |> ignore
|
|
||||||
b.Property<bool>("HasVpsInterface").HasColumnName("has_vps_interface") |> ignore
|
|
||||||
b.Property<string>("InterfaceAddress").HasColumnName("interface_address") |> ignore
|
|
||||||
b.Property<string>("Name").HasColumnName("church_name").IsRequired() |> ignore
|
|
||||||
b.Property<string>("State").HasColumnName("state").IsRequired().HasMaxLength(2) |> ignore
|
|
||||||
b.HasKey("Id") |> ignore
|
|
||||||
b.ToTable("church") |> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
modelBuilder.Entity (typeof<ListPreferences>, fun b ->
|
|
||||||
b.Property<Guid>("SmallGroupId").HasColumnName("small_group_id") |> ignore
|
|
||||||
b.Property<string>("AsOfDateDisplay").HasColumnName("as_of_date_display").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("N").HasMaxLength(1) |> ignore
|
|
||||||
b.Property<int>("DaysToExpire").HasColumnName("days_to_expire").ValueGeneratedOnAdd().HasDefaultValue(14) |> ignore
|
|
||||||
b.Property<int>("DaysToKeepNew").HasColumnName("days_to_keep_new").ValueGeneratedOnAdd().HasDefaultValue(7) |> ignore
|
|
||||||
b.Property<string>("DefaultEmailType").HasColumnName("default_email_type").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("H") |> ignore
|
|
||||||
b.Property<string>("EmailFromAddress").HasColumnName("email_from_address").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("prayer@djs-consulting.com") |> ignore
|
|
||||||
b.Property<string>("EmailFromName").HasColumnName("email_from_name").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("PrayerTracker") |> ignore
|
|
||||||
b.Property<string>("Fonts").HasColumnName("fonts").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("Century Gothic,Tahoma,Luxi Sans,sans-serif") |> ignore
|
|
||||||
b.Property<string>("GroupPassword").HasColumnName("group_password").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("") |> ignore
|
|
||||||
b.Property<string>("HeadingColor").HasColumnName("heading_color").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("maroon") |> ignore
|
|
||||||
b.Property<int>("HeadingFontSize").HasColumnName("heading_font_size").ValueGeneratedOnAdd().HasDefaultValue(16) |> ignore
|
|
||||||
b.Property<bool>("IsPublic").HasColumnName("is_public").ValueGeneratedOnAdd().HasDefaultValue(false) |> ignore
|
|
||||||
b.Property<string>("LineColor").HasColumnName("line_color").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("navy") |> ignore
|
|
||||||
b.Property<int>("LongTermUpdateWeeks").HasColumnName("long_term_update_weeks").ValueGeneratedOnAdd().HasDefaultValue(4) |> ignore
|
|
||||||
b.Property<int>("PageSize").HasColumnName("page_size").IsRequired().ValueGeneratedOnAdd().HasDefaultValue(100) |> ignore
|
|
||||||
b.Property<string>("RequestSort").HasColumnName("request_sort").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("D").HasMaxLength(1) |> ignore
|
|
||||||
b.Property<int>("TextFontSize").HasColumnName("text_font_size").ValueGeneratedOnAdd().HasDefaultValue(12) |> ignore
|
|
||||||
b.Property<string>("TimeZoneId").HasColumnName("time_zone_id").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("America/Denver") |> ignore
|
|
||||||
b.HasKey("SmallGroupId") |> ignore
|
|
||||||
b.HasIndex("TimeZoneId").HasDatabaseName "ix_list_preference_time_zone_id" |> ignore
|
|
||||||
b.ToTable("list_preference") |> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
modelBuilder.Entity (typeof<Member>, fun b ->
|
|
||||||
b.Property<Guid>("Id").HasColumnName("id").ValueGeneratedOnAdd() |> ignore
|
|
||||||
b.Property<string>("Email").HasColumnName("email").IsRequired() |> ignore
|
|
||||||
b.Property<string>("Format").HasColumnName("email_format") |> ignore
|
|
||||||
b.Property<string>("Name").HasColumnName("member_name").IsRequired() |> ignore
|
|
||||||
b.Property<Guid>("SmallGroupId").HasColumnName("small_group_id") |> ignore
|
|
||||||
b.HasKey("Id") |> ignore
|
|
||||||
b.HasIndex("SmallGroupId").HasDatabaseName "ix_member_small_group_id" |> ignore
|
|
||||||
b.ToTable("member") |> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
modelBuilder.Entity (typeof<PrayerRequest>, fun b ->
|
|
||||||
b.Property<Guid>("Id").HasColumnName("id").ValueGeneratedOnAdd() |> ignore
|
|
||||||
b.Property<DateTime>("EnteredDate").HasColumnName("entered_date").IsRequired() |> ignore
|
|
||||||
b.Property<string>("Expiration").HasColumnName("expiration").IsRequired().HasMaxLength 1 |> ignore
|
|
||||||
b.Property<bool>("NotifyChaplain").HasColumnName("notify_chaplain") |> ignore
|
|
||||||
b.Property<string>("RequestType").HasColumnName("request_type").IsRequired().HasMaxLength 1 |> ignore
|
|
||||||
b.Property<string>("Requestor").HasColumnName("requestor") |> ignore
|
|
||||||
b.Property<Guid>("SmallGroupId").HasColumnName("small_group_id") |> ignore
|
|
||||||
b.Property<string>("Text").HasColumnName("request_text").IsRequired() |> ignore
|
|
||||||
b.Property<DateTime>("UpdatedDate").HasColumnName("updated_date") |> ignore
|
|
||||||
b.Property<Guid>("UserId").HasColumnName("user_id") |> ignore
|
|
||||||
b.HasKey("Id") |> ignore
|
|
||||||
b.HasIndex("SmallGroupId").HasDatabaseName "ix_prayer_request_small_group_id" |> ignore
|
|
||||||
b.HasIndex("UserId").HasDatabaseName "ix_prayer_request_user_id" |> ignore
|
|
||||||
b.ToTable("prayer_request") |> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
modelBuilder.Entity (typeof<SmallGroup>, fun b ->
|
|
||||||
b.Property<Guid>("Id").HasColumnName("id").ValueGeneratedOnAdd() |> ignore
|
|
||||||
b.Property<Guid>("ChurchId").HasColumnName("church_id") |> ignore
|
|
||||||
b.Property<string>("Name").HasColumnName("group_name").IsRequired() |> ignore
|
|
||||||
b.HasKey("Id") |> ignore
|
|
||||||
b.HasIndex("ChurchId").HasDatabaseName "ix_small_group_church_id" |> ignore
|
|
||||||
b.ToTable("small_group") |> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
modelBuilder.Entity (typeof<TimeZone>, fun b ->
|
|
||||||
b.Property<string>("Id").HasColumnName("id").ValueGeneratedOnAdd() |> ignore
|
|
||||||
b.Property<string>("Description").HasColumnName("description").IsRequired() |> ignore
|
|
||||||
b.Property<bool>("IsActive").HasColumnName("is_active") |> ignore
|
|
||||||
b.Property<int>("SortOrder").HasColumnName("sort_order") |> ignore
|
|
||||||
b.HasKey("Id") |> ignore
|
|
||||||
b.ToTable("time_zone") |> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
modelBuilder.Entity (typeof<User>, fun b ->
|
|
||||||
b.Property<Guid>("Id").HasColumnName("id").ValueGeneratedOnAdd() |> ignore
|
|
||||||
b.Property<string>("Email").HasColumnName("email").IsRequired() |> ignore
|
|
||||||
b.Property<string>("FirstName").HasColumnName("first_name").IsRequired() |> ignore
|
|
||||||
b.Property<bool>("IsAdmin").HasColumnName("is_admin") |> ignore
|
|
||||||
b.Property<string>("LastName").HasColumnName("last_name").IsRequired() |> ignore
|
|
||||||
b.Property<string>("PasswordHash").HasColumnName("password_hash").IsRequired() |> ignore
|
|
||||||
b.Property<Guid>("Salt").HasColumnName("salt") |> ignore
|
|
||||||
b.Property<DateTime>("LastSeen").HasColumnName("last_seen") |> ignore
|
|
||||||
b.HasKey("Id") |> ignore
|
|
||||||
b.ToTable("pt_user") |> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
modelBuilder.Entity (typeof<UserSmallGroup>, fun b ->
|
|
||||||
b.Property<Guid>("UserId").HasColumnName("user_id") |> ignore
|
|
||||||
b.Property<Guid>("SmallGroupId").HasColumnName("small_group_id") |> ignore
|
|
||||||
b.HasKey("UserId", "SmallGroupId") |> ignore
|
|
||||||
b.HasIndex("SmallGroupId").HasDatabaseName "ix_user_small_group_small_group_id" |> ignore
|
|
||||||
b.ToTable("user_small_group") |> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
modelBuilder.Entity (typeof<ListPreferences>, fun b ->
|
|
||||||
b.HasOne("PrayerTracker.Entities.SmallGroup")
|
|
||||||
.WithOne("Preferences")
|
|
||||||
.HasForeignKey("PrayerTracker.Entities.ListPreferences", "SmallGroupId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
|> ignore
|
|
||||||
b.HasOne("PrayerTracker.Entities.TimeZone", "TimeZone")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("TimeZoneId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
|> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
modelBuilder.Entity (typeof<Member>, fun b ->
|
|
||||||
b.HasOne("PrayerTracker.Entities.SmallGroup", "SmallGroup")
|
|
||||||
.WithMany("Members")
|
|
||||||
.HasForeignKey("SmallGroupId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
|> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
modelBuilder.Entity (typeof<PrayerRequest>, fun b ->
|
|
||||||
b.HasOne("PrayerTracker.Entities.SmallGroup", "SmallGroup")
|
|
||||||
.WithMany("PrayerRequests")
|
|
||||||
.HasForeignKey("SmallGroupId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
|> ignore
|
|
||||||
b.HasOne("PrayerTracker.Entities.User", "User")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("UserId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
|> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
modelBuilder.Entity (typeof<SmallGroup>, fun b ->
|
|
||||||
b.HasOne("PrayerTracker.Entities.Church", "Church")
|
|
||||||
.WithMany("SmallGroups")
|
|
||||||
.HasForeignKey("ChurchId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
|> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
modelBuilder.Entity (typeof<UserSmallGroup>, fun b ->
|
|
||||||
b.HasOne("PrayerTracker.Entities.SmallGroup", "SmallGroup")
|
|
||||||
.WithMany("Users")
|
|
||||||
.HasForeignKey("SmallGroupId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
|> ignore
|
|
||||||
b.HasOne("PrayerTracker.Entities.User", "User")
|
|
||||||
.WithMany("SmallGroups")
|
|
||||||
.HasForeignKey("UserId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
|> ignore)
|
|
||||||
|> ignore
|
|
|
@ -1,175 +0,0 @@
|
||||||
namespace PrayerTracker.Migrations
|
|
||||||
|
|
||||||
open System
|
|
||||||
open Microsoft.EntityFrameworkCore
|
|
||||||
open Microsoft.EntityFrameworkCore.Infrastructure
|
|
||||||
open Npgsql.EntityFrameworkCore.PostgreSQL.Metadata
|
|
||||||
open PrayerTracker
|
|
||||||
open PrayerTracker.Entities
|
|
||||||
|
|
||||||
[<DbContext (typeof<AppDbContext>)>]
|
|
||||||
type AppDbContextModelSnapshot () =
|
|
||||||
inherit ModelSnapshot ()
|
|
||||||
|
|
||||||
override _.BuildModel (modelBuilder : ModelBuilder) =
|
|
||||||
modelBuilder
|
|
||||||
.HasDefaultSchema("pt")
|
|
||||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn)
|
|
||||||
.HasAnnotation("ProductVersion", "1.1.0-rtm-22752")
|
|
||||||
|> ignore
|
|
||||||
modelBuilder.Entity (typeof<Church>, fun b ->
|
|
||||||
b.Property<Guid>("Id").HasColumnName("id").ValueGeneratedOnAdd() |> ignore
|
|
||||||
b.Property<string>("City").HasColumnName("city").IsRequired() |> ignore
|
|
||||||
b.Property<bool>("HasVpsInterface").HasColumnName("has_vps_interface") |> ignore
|
|
||||||
b.Property<string>("InterfaceAddress").HasColumnName("interface_address") |> ignore
|
|
||||||
b.Property<string>("Name").HasColumnName("church_name").IsRequired() |> ignore
|
|
||||||
b.Property<string>("State").HasColumnName("state").IsRequired().HasMaxLength(2) |> ignore
|
|
||||||
b.HasKey("Id") |> ignore
|
|
||||||
b.ToTable("church") |> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
modelBuilder.Entity (typeof<ListPreferences>, fun b ->
|
|
||||||
b.Property<Guid>("SmallGroupId").HasColumnName("small_group_id") |> ignore
|
|
||||||
b.Property<string>("AsOfDateDisplay").HasColumnName("as_of_date_display").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("N").HasMaxLength(1) |> ignore
|
|
||||||
b.Property<int>("DaysToExpire").HasColumnName("days_to_expire").ValueGeneratedOnAdd().HasDefaultValue(14) |> ignore
|
|
||||||
b.Property<int>("DaysToKeepNew").HasColumnName("days_to_keep_new").ValueGeneratedOnAdd().HasDefaultValue(7) |> ignore
|
|
||||||
b.Property<string>("DefaultEmailType").HasColumnName("default_email_type").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("H") |> ignore
|
|
||||||
b.Property<string>("EmailFromAddress").HasColumnName("email_from_address").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("prayer@djs-consulting.com") |> ignore
|
|
||||||
b.Property<string>("EmailFromName").HasColumnName("email_from_name").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("PrayerTracker") |> ignore
|
|
||||||
b.Property<string>("Fonts").HasColumnName("fonts").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("Century Gothic,Tahoma,Luxi Sans,sans-serif") |> ignore
|
|
||||||
b.Property<string>("GroupPassword").HasColumnName("group_password").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("") |> ignore
|
|
||||||
b.Property<string>("HeadingColor").HasColumnName("heading_color").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("maroon") |> ignore
|
|
||||||
b.Property<int>("HeadingFontSize").HasColumnName("heading_font_size").ValueGeneratedOnAdd().HasDefaultValue(16) |> ignore
|
|
||||||
b.Property<bool>("IsPublic").HasColumnName("is_public").ValueGeneratedOnAdd().HasDefaultValue(false) |> ignore
|
|
||||||
b.Property<string>("LineColor").HasColumnName("line_color").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("navy") |> ignore
|
|
||||||
b.Property<int>("LongTermUpdateWeeks").HasColumnName("long_term_update_weeks").ValueGeneratedOnAdd().HasDefaultValue(4) |> ignore
|
|
||||||
b.Property<int>("PageSize").HasColumnName("page_size").IsRequired().ValueGeneratedOnAdd().HasDefaultValue(100) |> ignore
|
|
||||||
b.Property<string>("RequestSort").HasColumnName("request_sort").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("D").HasMaxLength(1) |> ignore
|
|
||||||
b.Property<int>("TextFontSize").HasColumnName("text_font_size").ValueGeneratedOnAdd().HasDefaultValue(12) |> ignore
|
|
||||||
b.Property<string>("TimeZoneId").HasColumnName("time_zone_id").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("America/Denver") |> ignore
|
|
||||||
b.HasKey("SmallGroupId") |> ignore
|
|
||||||
b.HasIndex("TimeZoneId").HasDatabaseName "ix_list_preference_time_zone_id" |> ignore
|
|
||||||
b.ToTable("list_preference") |> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
modelBuilder.Entity (typeof<Member>, fun b ->
|
|
||||||
b.Property<Guid>("Id").HasColumnName("id").ValueGeneratedOnAdd() |> ignore
|
|
||||||
b.Property<string>("Email").HasColumnName("email").IsRequired() |> ignore
|
|
||||||
b.Property<string>("Format").HasColumnName("email_format") |> ignore
|
|
||||||
b.Property<string>("Name").HasColumnName("member_name").IsRequired() |> ignore
|
|
||||||
b.Property<Guid>("SmallGroupId").HasColumnName("small_group_id") |> ignore
|
|
||||||
b.HasKey("Id") |> ignore
|
|
||||||
b.HasIndex("SmallGroupId").HasDatabaseName "ix_member_small_group_id" |> ignore
|
|
||||||
b.ToTable("member") |> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
modelBuilder.Entity (typeof<PrayerRequest>, fun b ->
|
|
||||||
b.Property<Guid>("Id").HasColumnName("id").ValueGeneratedOnAdd() |> ignore
|
|
||||||
b.Property<DateTime>("EnteredDate").HasColumnName("entered_date").IsRequired() |> ignore
|
|
||||||
b.Property<string>("Expiration").HasColumnName("expiration").IsRequired().HasMaxLength 1 |> ignore
|
|
||||||
b.Property<bool>("NotifyChaplain").HasColumnName("notify_chaplain") |> ignore
|
|
||||||
b.Property<string>("RequestType").HasColumnName("request_type").IsRequired().HasMaxLength 1 |> ignore
|
|
||||||
b.Property<string>("Requestor").HasColumnName("requestor") |> ignore
|
|
||||||
b.Property<Guid>("SmallGroupId").HasColumnName("small_group_id") |> ignore
|
|
||||||
b.Property<string>("Text").HasColumnName("request_text").IsRequired() |> ignore
|
|
||||||
b.Property<DateTime>("UpdatedDate").HasColumnName("updated_date") |> ignore
|
|
||||||
b.Property<Guid>("UserId").HasColumnName("user_id") |> ignore
|
|
||||||
b.HasKey("Id") |> ignore
|
|
||||||
b.HasIndex("SmallGroupId").HasDatabaseName "ix_prayer_request_small_group_id" |> ignore
|
|
||||||
b.HasIndex("UserId").HasDatabaseName "ix_prayer_request_user_id" |> ignore
|
|
||||||
b.ToTable("prayer_request") |> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
modelBuilder.Entity (typeof<SmallGroup>, fun b ->
|
|
||||||
b.Property<Guid>("Id").HasColumnName("id").ValueGeneratedOnAdd() |> ignore
|
|
||||||
b.Property<Guid>("ChurchId").HasColumnName("church_id") |> ignore
|
|
||||||
b.Property<string>("Name").HasColumnName("group_name").IsRequired() |> ignore
|
|
||||||
b.HasKey("Id") |> ignore
|
|
||||||
b.HasIndex("ChurchId").HasDatabaseName "ix_small_group_church_id" |> ignore
|
|
||||||
b.ToTable("small_group") |> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
modelBuilder.Entity (typeof<TimeZone>, fun b ->
|
|
||||||
b.Property<string>("Id").HasColumnName("id").ValueGeneratedOnAdd() |> ignore
|
|
||||||
b.Property<string>("Description").HasColumnName("description").IsRequired() |> ignore
|
|
||||||
b.Property<bool>("IsActive").HasColumnName("is_active") |> ignore
|
|
||||||
b.Property<int>("SortOrder").HasColumnName("sort_order") |> ignore
|
|
||||||
b.HasKey("Id") |> ignore
|
|
||||||
b.ToTable("time_zone") |> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
modelBuilder.Entity (typeof<User>, fun b ->
|
|
||||||
b.Property<Guid>("Id").HasColumnName("id").ValueGeneratedOnAdd() |> ignore
|
|
||||||
b.Property<string>("Email").HasColumnName("email").IsRequired() |> ignore
|
|
||||||
b.Property<string>("FirstName").HasColumnName("first_name").IsRequired() |> ignore
|
|
||||||
b.Property<bool>("IsAdmin").HasColumnName("is_admin") |> ignore
|
|
||||||
b.Property<string>("LastName").HasColumnName("last_name").IsRequired() |> ignore
|
|
||||||
b.Property<string>("PasswordHash").HasColumnName("password_hash").IsRequired() |> ignore
|
|
||||||
b.Property<Guid>("Salt").HasColumnName("salt") |> ignore
|
|
||||||
b.Property<DateTime>("LastSeen").HasColumnName("last_seen") |> ignore
|
|
||||||
b.HasKey("Id") |> ignore
|
|
||||||
b.ToTable("pt_user") |> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
modelBuilder.Entity (typeof<UserSmallGroup>, fun b ->
|
|
||||||
b.Property<Guid>("UserId").HasColumnName("user_id") |> ignore
|
|
||||||
b.Property<Guid>("SmallGroupId").HasColumnName("small_group_id") |> ignore
|
|
||||||
b.HasKey("UserId", "SmallGroupId") |> ignore
|
|
||||||
b.HasIndex("SmallGroupId").HasDatabaseName "ix_user_small_group_small_group_id" |> ignore
|
|
||||||
b.ToTable("user_small_group") |> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
modelBuilder.Entity (typeof<ListPreferences>, fun b ->
|
|
||||||
b.HasOne("PrayerTracker.Entities.SmallGroup")
|
|
||||||
.WithOne("Preferences")
|
|
||||||
.HasForeignKey("PrayerTracker.Entities.ListPreferences", "smallGroupId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
|> ignore
|
|
||||||
b.HasOne("PrayerTracker.Entities.TimeZone", "TimeZone")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("TimeZoneId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
|> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
modelBuilder.Entity (typeof<Member>, fun b ->
|
|
||||||
b.HasOne("PrayerTracker.Entities.SmallGroup", "SmallGroup")
|
|
||||||
.WithMany("Members")
|
|
||||||
.HasForeignKey("SmallGroupId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
|> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
modelBuilder.Entity (typeof<PrayerRequest>, fun b ->
|
|
||||||
b.HasOne("PrayerTracker.Entities.SmallGroup", "SmallGroup")
|
|
||||||
.WithMany("PrayerRequests")
|
|
||||||
.HasForeignKey("SmallGroupId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
|> ignore
|
|
||||||
b.HasOne("PrayerTracker.Entities.User", "User")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("UserId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
|> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
modelBuilder.Entity (typeof<SmallGroup>, fun b ->
|
|
||||||
b.HasOne("PrayerTracker.Entities.Church", "Church")
|
|
||||||
.WithMany("SmallGroups")
|
|
||||||
.HasForeignKey("ChurchId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
|> ignore)
|
|
||||||
|> ignore
|
|
||||||
|
|
||||||
modelBuilder.Entity (typeof<UserSmallGroup>, fun b ->
|
|
||||||
b.HasOne("PrayerTracker.Entities.SmallGroup", "SmallGroup")
|
|
||||||
.WithMany("Users")
|
|
||||||
.HasForeignKey("SmallGroupId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
|> ignore
|
|
||||||
b.HasOne("PrayerTracker.Entities.User", "User")
|
|
||||||
.WithMany("SmallGroups")
|
|
||||||
.HasForeignKey("UserId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
|> ignore)
|
|
||||||
|> ignore
|
|
|
@ -7,18 +7,12 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Entities.fs" />
|
<Compile Include="Entities.fs" />
|
||||||
<Compile Include="Access.fs" />
|
<Compile Include="Access.fs" />
|
||||||
<Compile Include="AppDbContext.fs" />
|
|
||||||
<Compile Include="DataAccess.fs" />
|
|
||||||
<Compile Include="Migrations\20161217153124_InitialDatabase.fs" />
|
|
||||||
<Compile Include="Migrations\AppDbContextModelSnapshot.fs" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="FSharp.EFCore.OptionConverter" Version="1.0.0" />
|
|
||||||
<PackageReference Include="Giraffe" Version="6.0.0" />
|
<PackageReference Include="Giraffe" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.FSharpLu" Version="0.11.7" />
|
<PackageReference Include="Microsoft.FSharpLu" Version="0.11.7" />
|
||||||
<PackageReference Include="NodaTime" Version="3.1.0" />
|
<PackageReference Include="NodaTime" Version="3.1.1" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.6" />
|
|
||||||
<PackageReference Update="FSharp.Core" Version="6.0.5" />
|
<PackageReference Update="FSharp.Core" Version="6.0.5" />
|
||||||
<PackageReference Include="Npgsql.FSharp" Version="5.3.0" />
|
<PackageReference Include="Npgsql.FSharp" Version="5.3.0" />
|
||||||
<PackageReference Include="Npgsql.NodaTime" Version="6.0.6" />
|
<PackageReference Include="Npgsql.NodaTime" Version="6.0.6" />
|
||||||
|
|
|
@ -118,8 +118,6 @@ let listPreferencesTests =
|
||||||
Expect.isFalse mt.IsPublic "The isPublic flag should not have been set"
|
Expect.isFalse mt.IsPublic "The isPublic flag should not have been set"
|
||||||
Expect.equal (TimeZoneId.toString mt.TimeZoneId) "America/Denver"
|
Expect.equal (TimeZoneId.toString mt.TimeZoneId) "America/Denver"
|
||||||
"The default time zone should have been America/Denver"
|
"The default time zone should have been America/Denver"
|
||||||
Expect.equal (TimeZoneId.toString mt.TimeZone.Id) ""
|
|
||||||
"The default preferences should have included an empty time zone"
|
|
||||||
Expect.equal mt.PageSize 100 "The default page size should have been 100"
|
Expect.equal mt.PageSize 100 "The default page size should have been 100"
|
||||||
Expect.equal mt.AsOfDateDisplay NoDisplay "The as-of date display should have been No Display"
|
Expect.equal mt.AsOfDateDisplay NoDisplay "The as-of date display should have been No Display"
|
||||||
}
|
}
|
||||||
|
@ -135,7 +133,6 @@ let memberTests =
|
||||||
Expect.equal mt.Name "" "The member name should have been blank"
|
Expect.equal mt.Name "" "The member name should have been blank"
|
||||||
Expect.equal mt.Email "" "The member e-mail address should have been blank"
|
Expect.equal mt.Email "" "The member e-mail address should have been blank"
|
||||||
Expect.isNone mt.Format "The preferred e-mail format should not exist"
|
Expect.isNone mt.Format "The preferred e-mail format should not exist"
|
||||||
Expect.equal mt.SmallGroup.Id.Value Guid.Empty "The small group should have been an empty one"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -156,8 +153,6 @@ let prayerRequestTests =
|
||||||
Expect.equal mt.Text "" "The request text should have been blank"
|
Expect.equal mt.Text "" "The request text should have been blank"
|
||||||
Expect.isFalse mt.NotifyChaplain "The notify chaplain flag should not have been set"
|
Expect.isFalse mt.NotifyChaplain "The notify chaplain flag should not have been set"
|
||||||
Expect.equal mt.Expiration Automatic "The expiration should have been Automatic"
|
Expect.equal mt.Expiration Automatic "The expiration should have been Automatic"
|
||||||
Expect.equal mt.User.Id.Value Guid.Empty "The user should have been an empty one"
|
|
||||||
Expect.equal mt.SmallGroup.Id.Value Guid.Empty "The small group should have been an empty one"
|
|
||||||
}
|
}
|
||||||
test "isExpired always returns false for expecting requests" {
|
test "isExpired always returns false for expecting requests" {
|
||||||
PrayerRequest.isExpired (localDateNow ()) SmallGroup.empty
|
PrayerRequest.isExpired (localDateNow ()) SmallGroup.empty
|
||||||
|
@ -294,13 +289,6 @@ let smallGroupTests =
|
||||||
Expect.equal mt.Id.Value Guid.Empty "The small group ID should have been an empty GUID"
|
Expect.equal mt.Id.Value Guid.Empty "The small group ID should have been an empty GUID"
|
||||||
Expect.equal mt.ChurchId.Value Guid.Empty "The church ID should have been an empty GUID"
|
Expect.equal mt.ChurchId.Value Guid.Empty "The church ID should have been an empty GUID"
|
||||||
Expect.equal mt.Name "" "The name should have been blank"
|
Expect.equal mt.Name "" "The name should have been blank"
|
||||||
Expect.equal mt.Church.Id.Value Guid.Empty "The church should have been an empty one"
|
|
||||||
Expect.isNotNull mt.Members "The members navigation property should not be null"
|
|
||||||
Expect.isEmpty mt.Members "There should be no members for an empty small group"
|
|
||||||
Expect.isNotNull mt.PrayerRequests "The prayer requests navigation property should not be null"
|
|
||||||
Expect.isEmpty mt.PrayerRequests "There should be no prayer requests for an empty small group"
|
|
||||||
Expect.isNotNull mt.Users "The users navigation property should not be null"
|
|
||||||
Expect.isEmpty mt.Users "There should be no users for an empty small group"
|
|
||||||
}
|
}
|
||||||
yield! testFixture withFakeClock [
|
yield! testFixture withFakeClock [
|
||||||
"LocalTimeNow adjusts the time ahead of UTC",
|
"LocalTimeNow adjusts the time ahead of UTC",
|
||||||
|
@ -309,7 +297,6 @@ let smallGroupTests =
|
||||||
{ SmallGroup.empty with
|
{ SmallGroup.empty with
|
||||||
Preferences = { ListPreferences.empty with TimeZoneId = TimeZoneId "Europe/Berlin" }
|
Preferences = { ListPreferences.empty with TimeZoneId = TimeZoneId "Europe/Berlin" }
|
||||||
}
|
}
|
||||||
let tz = DateTimeZoneProviders.Tzdb["Europe/Berlin"]
|
|
||||||
Expect.isGreaterThan (SmallGroup.localTimeNow clock grp) (now.InUtc().LocalDateTime)
|
Expect.isGreaterThan (SmallGroup.localTimeNow clock grp) (now.InUtc().LocalDateTime)
|
||||||
"UTC to Europe/Berlin should have added hours"
|
"UTC to Europe/Berlin should have added hours"
|
||||||
"LocalTimeNow adjusts the time behind UTC",
|
"LocalTimeNow adjusts the time behind UTC",
|
||||||
|
@ -336,18 +323,6 @@ let smallGroupTests =
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
[<Tests>]
|
|
||||||
let timeZoneTests =
|
|
||||||
testList "TimeZone" [
|
|
||||||
test "empty is as expected" {
|
|
||||||
let mt = TimeZone.empty
|
|
||||||
Expect.equal (TimeZoneId.toString mt.Id) "" "The time zone ID should have been blank"
|
|
||||||
Expect.equal mt.Description "" "The description should have been blank"
|
|
||||||
Expect.equal mt.SortOrder 0 "The sort order should have been zero"
|
|
||||||
Expect.isFalse mt.IsActive "The is-active flag should not have been set"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
[<Tests>]
|
[<Tests>]
|
||||||
let userTests =
|
let userTests =
|
||||||
testList "User" [
|
testList "User" [
|
||||||
|
@ -359,9 +334,6 @@ let userTests =
|
||||||
Expect.equal mt.Email "" "The e-mail address should have been blank"
|
Expect.equal mt.Email "" "The e-mail address should have been blank"
|
||||||
Expect.isFalse mt.IsAdmin "The is admin flag should not have been set"
|
Expect.isFalse mt.IsAdmin "The is admin flag should not have been set"
|
||||||
Expect.equal mt.PasswordHash "" "The password hash should have been blank"
|
Expect.equal mt.PasswordHash "" "The password hash should have been blank"
|
||||||
Expect.isNone mt.Salt "The password salt should not exist"
|
|
||||||
Expect.isNotNull mt.SmallGroups "The small groups navigation property should not have been null"
|
|
||||||
Expect.isEmpty mt.SmallGroups "There should be no small groups for an empty user"
|
|
||||||
}
|
}
|
||||||
test "Name concatenates first and last names" {
|
test "Name concatenates first and last names" {
|
||||||
let user = { User.empty with FirstName = "Unit"; LastName = "Test" }
|
let user = { User.empty with FirstName = "Unit"; LastName = "Test" }
|
||||||
|
@ -376,7 +348,5 @@ let userSmallGroupTests =
|
||||||
let mt = UserSmallGroup.empty
|
let mt = UserSmallGroup.empty
|
||||||
Expect.equal mt.UserId.Value Guid.Empty "The user ID should have been an empty GUID"
|
Expect.equal mt.UserId.Value Guid.Empty "The user ID should have been an empty GUID"
|
||||||
Expect.equal mt.SmallGroupId.Value Guid.Empty "The small group ID should have been an empty GUID"
|
Expect.equal mt.SmallGroupId.Value Guid.Empty "The small group ID should have been an empty GUID"
|
||||||
Expect.equal mt.User.Id.Value Guid.Empty "The user should have been an empty one"
|
|
||||||
Expect.equal mt.SmallGroup.Id.Value Guid.Empty "The small group should have been an empty one"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Expecto" Version="9.0.4" />
|
<PackageReference Include="Expecto" Version="9.0.4" />
|
||||||
<PackageReference Include="Expecto.VisualStudio.TestAdapter" Version="10.0.2" />
|
|
||||||
<PackageReference Include="NodaTime.Testing" Version="3.1.0" />
|
<PackageReference Include="NodaTime.Testing" Version="3.1.0" />
|
||||||
<PackageReference Update="FSharp.Core" Version="6.0.5" />
|
<PackageReference Update="FSharp.Core" Version="6.0.5" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -543,9 +543,9 @@ let requestListTests =
|
||||||
let curReqHtml =
|
let curReqHtml =
|
||||||
[ "<ul>"
|
[ "<ul>"
|
||||||
"""<li style="list-style-type:circle;font-family:Century Gothic,Tahoma,Luxi Sans,sans-serif;font-size:12pt;padding-bottom:.25em;">"""
|
"""<li style="list-style-type:circle;font-family:Century Gothic,Tahoma,Luxi Sans,sans-serif;font-size:12pt;padding-bottom:.25em;">"""
|
||||||
"<strong>Zeb</strong> — zyx</li>"
|
"<strong>Zeb</strong> – zyx</li>"
|
||||||
"""<li style="list-style-type:disc;font-family:Century Gothic,Tahoma,Luxi Sans,sans-serif;font-size:12pt;padding-bottom:.25em;">"""
|
"""<li style="list-style-type:disc;font-family:Century Gothic,Tahoma,Luxi Sans,sans-serif;font-size:12pt;padding-bottom:.25em;">"""
|
||||||
"<strong>Aaron</strong> — abc</li></ul>"
|
"<strong>Aaron</strong> – abc</li></ul>"
|
||||||
]
|
]
|
||||||
|> String.concat ""
|
|> String.concat ""
|
||||||
Expect.stringContains html curReqHtml """Expected HTML for "Current Requests" requests not found"""
|
Expect.stringContains html curReqHtml """Expected HTML for "Current Requests" requests not found"""
|
||||||
|
@ -582,7 +582,7 @@ let requestListTests =
|
||||||
|> String.concat ""
|
|> String.concat ""
|
||||||
Expect.stringContains html lstHeading "Expected HTML for the list heading not found"
|
Expect.stringContains html lstHeading "Expected HTML for the list heading not found"
|
||||||
// spot check; without header test tests this exhaustively
|
// spot check; without header test tests this exhaustively
|
||||||
Expect.stringContains html "<strong>Zeb</strong> — zyx</li>" "Expected requests not found"
|
Expect.stringContains html "<strong>Zeb</strong> – zyx</li>" "Expected requests not found"
|
||||||
"AsHtml succeeds with short as-of date",
|
"AsHtml succeeds with short as-of date",
|
||||||
fun reqList ->
|
fun reqList ->
|
||||||
let htmlList =
|
let htmlList =
|
||||||
|
@ -595,7 +595,7 @@ let requestListTests =
|
||||||
let html = htmlList.AsHtml _s
|
let html = htmlList.AsHtml _s
|
||||||
let expected =
|
let expected =
|
||||||
htmlList.Requests[0].UpdatedDate.InUtc().Date.ToString ("d", null)
|
htmlList.Requests[0].UpdatedDate.InUtc().Date.ToString ("d", null)
|
||||||
|> sprintf """<strong>Zeb</strong> — zyx<i style="font-size:9.60pt"> (as of %s)</i>"""
|
|> sprintf """<strong>Zeb</strong> – zyx<i style="font-size:9.60pt"> (as of %s)</i>"""
|
||||||
// spot check; if one request has it, they all should
|
// spot check; if one request has it, they all should
|
||||||
Expect.stringContains html expected "Expected short as-of date not found"
|
Expect.stringContains html expected "Expected short as-of date not found"
|
||||||
"AsHtml succeeds with long as-of date",
|
"AsHtml succeeds with long as-of date",
|
||||||
|
@ -610,7 +610,7 @@ let requestListTests =
|
||||||
let html = htmlList.AsHtml _s
|
let html = htmlList.AsHtml _s
|
||||||
let expected =
|
let expected =
|
||||||
htmlList.Requests[0].UpdatedDate.InUtc().Date.ToString ("D", null)
|
htmlList.Requests[0].UpdatedDate.InUtc().Date.ToString ("D", null)
|
||||||
|> sprintf """<strong>Zeb</strong> — zyx<i style="font-size:9.60pt"> (as of %s)</i>"""
|
|> sprintf """<strong>Zeb</strong> – zyx<i style="font-size:9.60pt"> (as of %s)</i>"""
|
||||||
// spot check; if one request has it, they all should
|
// spot check; if one request has it, they all should
|
||||||
Expect.stringContains html expected "Expected long as-of date not found"
|
Expect.stringContains html expected "Expected long as-of date not found"
|
||||||
"AsText succeeds with no as-of date",
|
"AsText succeeds with no as-of date",
|
||||||
|
|
|
@ -14,13 +14,12 @@ type RequestStartMiddleware (next : RequestDelegate) =
|
||||||
open System
|
open System
|
||||||
open Microsoft.AspNetCore.Builder
|
open Microsoft.AspNetCore.Builder
|
||||||
open Microsoft.AspNetCore.Hosting
|
open Microsoft.AspNetCore.Hosting
|
||||||
|
open Microsoft.Extensions.Configuration
|
||||||
|
|
||||||
/// Module to hold configuration for the web app
|
/// Module to hold configuration for the web app
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module Configure =
|
module Configure =
|
||||||
|
|
||||||
open Microsoft.Extensions.Configuration
|
|
||||||
|
|
||||||
/// Set up the configuration for the app
|
/// Set up the configuration for the app
|
||||||
let configuration (ctx : WebHostBuilderContext) (cfg : IConfigurationBuilder) =
|
let configuration (ctx : WebHostBuilderContext) (cfg : IConfigurationBuilder) =
|
||||||
cfg.SetBasePath(ctx.HostingEnvironment.ContentRootPath)
|
cfg.SetBasePath(ctx.HostingEnvironment.ContentRootPath)
|
||||||
|
@ -39,7 +38,6 @@ module Configure =
|
||||||
open System.IO
|
open System.IO
|
||||||
open Microsoft.AspNetCore.Authentication.Cookies
|
open Microsoft.AspNetCore.Authentication.Cookies
|
||||||
open Microsoft.AspNetCore.Localization
|
open Microsoft.AspNetCore.Localization
|
||||||
open Microsoft.EntityFrameworkCore
|
|
||||||
open Microsoft.Extensions.DependencyInjection
|
open Microsoft.Extensions.DependencyInjection
|
||||||
open NeoSmart.Caching.Sqlite
|
open NeoSmart.Caching.Sqlite
|
||||||
open NodaTime
|
open NodaTime
|
||||||
|
@ -69,15 +67,6 @@ module Configure =
|
||||||
let _ = svc.AddAntiforgery ()
|
let _ = svc.AddAntiforgery ()
|
||||||
let _ = svc.AddRouting ()
|
let _ = svc.AddRouting ()
|
||||||
let _ = svc.AddSingleton<IClock> SystemClock.Instance
|
let _ = svc.AddSingleton<IClock> SystemClock.Instance
|
||||||
|
|
||||||
let config = svc.BuildServiceProvider().GetRequiredService<IConfiguration> ()
|
|
||||||
let _ =
|
|
||||||
svc.AddDbContext<AppDbContext> (
|
|
||||||
(fun options ->
|
|
||||||
options.UseNpgsql (
|
|
||||||
config.GetConnectionString "PrayerTracker", fun o -> o.UseNodaTime () |> ignore)
|
|
||||||
|> ignore),
|
|
||||||
ServiceLifetime.Scoped, ServiceLifetime.Singleton)
|
|
||||||
()
|
()
|
||||||
|
|
||||||
open Giraffe
|
open Giraffe
|
||||||
|
@ -194,10 +183,6 @@ module Configure =
|
||||||
let _ = app.UseDeveloperExceptionPage ()
|
let _ = app.UseDeveloperExceptionPage ()
|
||||||
()
|
()
|
||||||
else
|
else
|
||||||
try
|
|
||||||
use scope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope ()
|
|
||||||
scope.ServiceProvider.GetService<AppDbContext>().Database.Migrate ()
|
|
||||||
with _ -> () // om nom nom
|
|
||||||
let _ = app.UseGiraffeErrorHandler errorHandler
|
let _ = app.UseGiraffeErrorHandler errorHandler
|
||||||
()
|
()
|
||||||
|
|
||||||
|
@ -219,34 +204,55 @@ module Configure =
|
||||||
module App =
|
module App =
|
||||||
|
|
||||||
open System.Text
|
open System.Text
|
||||||
open Microsoft.EntityFrameworkCore
|
|
||||||
open Microsoft.Extensions.DependencyInjection
|
open Microsoft.Extensions.DependencyInjection
|
||||||
|
open Npgsql
|
||||||
|
open Npgsql.FSharp
|
||||||
|
open PrayerTracker.Entities
|
||||||
|
|
||||||
let migratePasswords (app : IWebHost) =
|
let migratePasswords (app : IWebHost) =
|
||||||
task {
|
task {
|
||||||
use db = app.Services.GetService<AppDbContext>()
|
let config = app.Services.GetService<IConfiguration> ()
|
||||||
let! v1Users = db.Users.FromSqlRaw("SELECT * FROM pt.pt_user WHERE salt IS NULL").ToListAsync ()
|
use conn = new NpgsqlConnection (config.GetConnectionString "PrayerTracker")
|
||||||
for user in v1Users do
|
do! conn.OpenAsync ()
|
||||||
|
let! v1Users =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query "SELECT id, password_hash FROM pt.pt_user WHERE salt IS NULL"
|
||||||
|
|> Sql.executeAsync (fun row -> UserId (row.uuid "id"), row.string "password_hash")
|
||||||
|
for userId, oldHash in v1Users do
|
||||||
let pw =
|
let pw =
|
||||||
[| 254uy
|
[| 254uy
|
||||||
yield! (Encoding.UTF8.GetBytes user.PasswordHash)
|
yield! (Encoding.UTF8.GetBytes oldHash)
|
||||||
|]
|
|]
|
||||||
|> Convert.ToBase64String
|
|> Convert.ToBase64String
|
||||||
db.UpdateEntry { user with PasswordHash = pw }
|
let! _ =
|
||||||
let! v1Count = db.SaveChangesAsync ()
|
conn
|
||||||
printfn $"Updated {v1Count} users with version 1 password"
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query "UPDATE pt.pt_user SET password_hash = @hash WHERE id = @id"
|
||||||
|
|> Sql.parameters [ "@id", Sql.uuid userId.Value; "@hash", Sql.string pw ]
|
||||||
|
|> Sql.executeNonQueryAsync
|
||||||
|
()
|
||||||
|
printfn $"Updated {v1Users.Length} users with version 1 password"
|
||||||
let! v2Users =
|
let! v2Users =
|
||||||
db.Users.FromSqlRaw("SELECT * FROM pt.pt_user WHERE salt IS NOT NULL").ToListAsync ()
|
conn
|
||||||
for user in v2Users do
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query "SELECT id, password_hash, salt FROM pt.pt_user WHERE salt IS NOT NULL"
|
||||||
|
|> Sql.executeAsync (fun row -> UserId (row.uuid "id"), row.string "password_hash", row.uuid "salt")
|
||||||
|
for userId, oldHash, salt in v2Users do
|
||||||
let pw =
|
let pw =
|
||||||
[| 255uy
|
[| 255uy
|
||||||
yield! (user.Salt.Value.ToByteArray ())
|
yield! (salt.ToByteArray ())
|
||||||
yield! (Encoding.UTF8.GetBytes user.PasswordHash)
|
yield! (Encoding.UTF8.GetBytes oldHash)
|
||||||
|]
|
|]
|
||||||
|> Convert.ToBase64String
|
|> Convert.ToBase64String
|
||||||
db.UpdateEntry { user with PasswordHash = pw }
|
let! _ =
|
||||||
let! v2Count = db.SaveChangesAsync ()
|
conn
|
||||||
printfn $"Updated {v2Count} users with version 2 password"
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query "UPDATE pt.pt_user SET password_hash = @hash WHERE id = @id"
|
||||||
|
|> Sql.parameters [ "@id", Sql.uuid userId.Value; "@hash", Sql.string pw ]
|
||||||
|
|> Sql.executeNonQueryAsync
|
||||||
|
()
|
||||||
|
printfn $"Updated {v2Users.Length} users with version 2 password"
|
||||||
} |> Async.AwaitTask |> Async.RunSynchronously
|
} |> Async.AwaitTask |> Async.RunSynchronously
|
||||||
|
|
||||||
open System.IO
|
open System.IO
|
||||||
|
|
|
@ -39,8 +39,7 @@ type ISession with
|
||||||
with get () = this.GetObject<User> Key.Session.currentUser |> Option.fromObject
|
with get () = this.GetObject<User> Key.Session.currentUser |> Option.fromObject
|
||||||
and set (v : User option) =
|
and set (v : User option) =
|
||||||
match v with
|
match v with
|
||||||
| Some user ->
|
| Some user -> this.SetObject Key.Session.currentUser { user with PasswordHash = "" }
|
||||||
this.SetObject Key.Session.currentUser { user with PasswordHash = ""; SmallGroups = ResizeArray() }
|
|
||||||
| None -> this.Remove Key.Session.currentUser
|
| None -> this.Remove Key.Session.currentUser
|
||||||
|
|
||||||
/// Current messages for the session
|
/// Current messages for the session
|
||||||
|
@ -74,7 +73,6 @@ open System.Threading.Tasks
|
||||||
open Giraffe
|
open Giraffe
|
||||||
open Microsoft.Extensions.Configuration
|
open Microsoft.Extensions.Configuration
|
||||||
open Npgsql
|
open Npgsql
|
||||||
open PrayerTracker
|
|
||||||
|
|
||||||
/// Extensions on the ASP.NET Core HTTP context
|
/// Extensions on the ASP.NET Core HTTP context
|
||||||
type HttpContext with
|
type HttpContext with
|
||||||
|
@ -87,9 +85,6 @@ type HttpContext with
|
||||||
return conn
|
return conn
|
||||||
})
|
})
|
||||||
|
|
||||||
/// The EF Core database context (via DI)
|
|
||||||
member this.Db = this.GetService<AppDbContext> ()
|
|
||||||
|
|
||||||
/// The PostgreSQL connection (configured via DI)
|
/// The PostgreSQL connection (configured via DI)
|
||||||
member this.Conn = backgroundTask {
|
member this.Conn = backgroundTask {
|
||||||
return! this.LazyConn.Force ()
|
return! this.LazyConn.Force ()
|
||||||
|
|
|
@ -126,7 +126,7 @@ let expire reqId : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task
|
||||||
| Ok req ->
|
| Ok req ->
|
||||||
let s = Views.I18N.localizer.Force ()
|
let s = Views.I18N.localizer.Force ()
|
||||||
let! conn = ctx.Conn
|
let! conn = ctx.Conn
|
||||||
do! PrayerRequests.updateExpiration { req with Expiration = Forced } conn
|
do! PrayerRequests.updateExpiration { req with Expiration = Forced } false conn
|
||||||
addInfo ctx s["Successfully {0} prayer request", s["Expired"].Value.ToLower ()]
|
addInfo ctx s["Successfully {0} prayer request", s["Expired"].Value.ToLower ()]
|
||||||
return! redirectTo false "/prayer-requests" next ctx
|
return! redirectTo false "/prayer-requests" next ctx
|
||||||
| Result.Error e -> return! e
|
| Result.Error e -> return! e
|
||||||
|
@ -177,16 +177,16 @@ let lists : HttpHandler = requireAccess [ AccessLevel.Public ] >=> fun next ctx
|
||||||
/// - OR -
|
/// - OR -
|
||||||
/// GET /prayer-requests?search=[search-query]
|
/// GET /prayer-requests?search=[search-query]
|
||||||
let maintain onlyActive : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task {
|
let maintain onlyActive : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task {
|
||||||
// TODO: stopped here
|
|
||||||
let group = ctx.Session.CurrentGroup.Value
|
let group = ctx.Session.CurrentGroup.Value
|
||||||
let pageNbr =
|
let pageNbr =
|
||||||
match ctx.GetQueryStringValue "page" with
|
match ctx.GetQueryStringValue "page" with
|
||||||
| Ok pg -> match Int32.TryParse pg with true, p -> p | false, _ -> 1
|
| Ok pg -> match Int32.TryParse pg with true, p -> p | false, _ -> 1
|
||||||
| Result.Error _ -> 1
|
| Result.Error _ -> 1
|
||||||
let! model = backgroundTask {
|
let! model = backgroundTask {
|
||||||
|
let! conn = ctx.Conn
|
||||||
match ctx.GetQueryStringValue "search" with
|
match ctx.GetQueryStringValue "search" with
|
||||||
| Ok search ->
|
| Ok search ->
|
||||||
let! reqs = ctx.Db.SearchRequestsForSmallGroup group search pageNbr
|
let! reqs = PrayerRequests.searchForGroup group search pageNbr conn
|
||||||
return
|
return
|
||||||
{ MaintainRequests.empty with
|
{ MaintainRequests.empty with
|
||||||
Requests = reqs
|
Requests = reqs
|
||||||
|
@ -194,7 +194,14 @@ let maintain onlyActive : HttpHandler = requireAccess [ User ] >=> fun next ctx
|
||||||
PageNbr = Some pageNbr
|
PageNbr = Some pageNbr
|
||||||
}
|
}
|
||||||
| Result.Error _ ->
|
| Result.Error _ ->
|
||||||
let! reqs = ctx.Db.AllRequestsForSmallGroup group ctx.Clock None onlyActive pageNbr
|
let! reqs =
|
||||||
|
PrayerRequests.forGroup
|
||||||
|
{ SmallGroup = group
|
||||||
|
Clock = ctx.Clock
|
||||||
|
ListDate = None
|
||||||
|
ActiveOnly = onlyActive
|
||||||
|
PageNumber = pageNbr
|
||||||
|
} conn
|
||||||
return
|
return
|
||||||
{ MaintainRequests.empty with
|
{ MaintainRequests.empty with
|
||||||
Requests = reqs
|
Requests = reqs
|
||||||
|
@ -222,8 +229,8 @@ let restore reqId : HttpHandler = requireAccess [ User ] >=> fun next ctx -> tas
|
||||||
match! findRequest ctx requestId with
|
match! findRequest ctx requestId with
|
||||||
| Ok req ->
|
| Ok req ->
|
||||||
let s = Views.I18N.localizer.Force ()
|
let s = Views.I18N.localizer.Force ()
|
||||||
ctx.Db.UpdateEntry { req with Expiration = Automatic; UpdatedDate = ctx.Now }
|
let! conn = ctx.Conn
|
||||||
let! _ = ctx.Db.SaveChangesAsync ()
|
do! PrayerRequests.updateExpiration { req with Expiration = Automatic; UpdatedDate = ctx.Now } true conn
|
||||||
addInfo ctx s["Successfully {0} prayer request", s["Restored"].Value.ToLower ()]
|
addInfo ctx s["Successfully {0} prayer request", s["Restored"].Value.ToLower ()]
|
||||||
return! redirectTo false "/prayer-requests" next ctx
|
return! redirectTo false "/prayer-requests" next ctx
|
||||||
| Result.Error e -> return! e
|
| Result.Error e -> return! e
|
||||||
|
@ -235,41 +242,42 @@ open System.Threading.Tasks
|
||||||
let save : HttpHandler = requireAccess [ User ] >=> validateCsrf >=> fun next ctx -> task {
|
let save : HttpHandler = requireAccess [ User ] >=> validateCsrf >=> fun next ctx -> task {
|
||||||
match! ctx.TryBindFormAsync<EditRequest> () with
|
match! ctx.TryBindFormAsync<EditRequest> () with
|
||||||
| Ok model ->
|
| Ok model ->
|
||||||
|
let group = ctx.Session.CurrentGroup.Value
|
||||||
|
let! conn = ctx.Conn
|
||||||
let! req =
|
let! req =
|
||||||
if model.IsNew then
|
if model.IsNew then
|
||||||
Task.FromResult (Some { PrayerRequest.empty with Id = (Guid.NewGuid >> PrayerRequestId) () })
|
{ PrayerRequest.empty with
|
||||||
else ctx.Db.TryRequestById (idFromShort PrayerRequestId model.RequestId)
|
Id = (Guid.NewGuid >> PrayerRequestId) ()
|
||||||
|
SmallGroupId = group.Id
|
||||||
|
UserId = ctx.User.UserId.Value
|
||||||
|
}
|
||||||
|
|> (Some >> Task.FromResult)
|
||||||
|
else PrayerRequests.tryById (idFromShort PrayerRequestId model.RequestId) conn
|
||||||
match req with
|
match req with
|
||||||
| Some pr ->
|
| Some pr when pr.SmallGroupId = group.Id ->
|
||||||
let upd8 =
|
let now = SmallGroup.localDateNow ctx.Clock group
|
||||||
|
let updated =
|
||||||
{ pr with
|
{ pr with
|
||||||
RequestType = PrayerRequestType.fromCode model.RequestType
|
RequestType = PrayerRequestType.fromCode model.RequestType
|
||||||
Requestor = match model.Requestor with Some x when x.Trim () = "" -> None | x -> x
|
Requestor = match model.Requestor with Some x when x.Trim () = "" -> None | x -> x
|
||||||
Text = ckEditorToText model.Text
|
Text = ckEditorToText model.Text
|
||||||
Expiration = Expiration.fromCode model.Expiration
|
Expiration = Expiration.fromCode model.Expiration
|
||||||
}
|
}
|
||||||
let group = ctx.Session.CurrentGroup.Value
|
|> function
|
||||||
let now = SmallGroup.localDateNow ctx.Clock group
|
| it when model.IsNew ->
|
||||||
match model.IsNew with
|
|
||||||
| true ->
|
|
||||||
let dt =
|
let dt =
|
||||||
(defaultArg (parseListDate model.EnteredDate) now)
|
(defaultArg (parseListDate model.EnteredDate) now)
|
||||||
.AtStartOfDayInZone(SmallGroup.timeZone group)
|
.AtStartOfDayInZone(SmallGroup.timeZone group)
|
||||||
.ToInstant()
|
.ToInstant()
|
||||||
{ upd8 with
|
{ it with EnteredDate = dt; UpdatedDate = dt }
|
||||||
SmallGroupId = group.Id
|
| it when defaultArg model.SkipDateUpdate false -> it
|
||||||
UserId = ctx.User.UserId.Value
|
| it -> { it with UpdatedDate = ctx.Now }
|
||||||
EnteredDate = dt
|
do! PrayerRequests.save updated conn
|
||||||
UpdatedDate = dt
|
|
||||||
}
|
|
||||||
| false when defaultArg model.SkipDateUpdate false -> upd8
|
|
||||||
| false -> { upd8 with UpdatedDate = ctx.Now }
|
|
||||||
|> if model.IsNew then ctx.Db.AddEntry else ctx.Db.UpdateEntry
|
|
||||||
let! _ = ctx.Db.SaveChangesAsync ()
|
|
||||||
let s = Views.I18N.localizer.Force ()
|
let s = Views.I18N.localizer.Force ()
|
||||||
let act = if model.IsNew then "Added" else "Updated"
|
let act = if model.IsNew then "Added" else "Updated"
|
||||||
addInfo ctx s["Successfully {0} prayer request", s[act].Value.ToLower ()]
|
addInfo ctx s["Successfully {0} prayer request", s[act].Value.ToLower ()]
|
||||||
return! redirectTo false "/prayer-requests" next ctx
|
return! redirectTo false "/prayer-requests" next ctx
|
||||||
|
| Some _
|
||||||
| None -> return! fourOhFour ctx
|
| None -> return! fourOhFour ctx
|
||||||
| Result.Error e -> return! bindError e next ctx
|
| Result.Error e -> return! bindError e next ctx
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,9 +28,7 @@
|
||||||
<PackageReference Include="Giraffe.Htmx" Version="1.8.0" />
|
<PackageReference Include="Giraffe.Htmx" Version="1.8.0" />
|
||||||
<PackageReference Include="NeoSmart.Caching.Sqlite" Version="6.0.1" />
|
<PackageReference Include="NeoSmart.Caching.Sqlite" Version="6.0.1" />
|
||||||
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.0.0" />
|
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.0.0" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.6" />
|
|
||||||
<PackageReference Update="FSharp.Core" Version="6.0.5" />
|
<PackageReference Update="FSharp.Core" Version="6.0.5" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="6.0.6" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user