Version 8 #43

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

View File

@ -231,7 +231,7 @@ type AppDbContext with
let! usr = this.Users.Include(fun u -> u.SmallGroups).SingleOrDefaultAsync (fun u -> u.Id = userId) let! usr = this.Users.Include(fun u -> u.SmallGroups).SingleOrDefaultAsync (fun u -> u.Id = userId)
return Option.fromObject usr return Option.fromObject usr
} }
/// Get a list of all users /// Get a list of all users
member this.AllUsers () = backgroundTask { member this.AllUsers () = backgroundTask {
let! users = this.Users.OrderBy(fun u -> u.LastName).ThenBy(fun u -> u.FirstName).ToListAsync () let! users = this.Users.OrderBy(fun u -> u.LastName).ThenBy(fun u -> u.FirstName).ToListAsync ()

View File

@ -375,7 +375,6 @@ type ChurchStats =
(*-- ENTITIES --*) (*-- ENTITIES --*)
open System.Collections.Generic
open FSharp.EFCore.OptionConverter open FSharp.EFCore.OptionConverter
open Microsoft.EntityFrameworkCore open Microsoft.EntityFrameworkCore
open NodaTime open NodaTime
@ -401,7 +400,7 @@ type [<CLIMutable; NoComparison; NoEquality>] Church =
InterfaceAddress : string option InterfaceAddress : string option
/// Small groups for this church /// Small groups for this church
SmallGroups : ICollection<SmallGroup> SmallGroups : ResizeArray<SmallGroup>
} }
with with
/// An empty church /// An empty church
@ -413,7 +412,7 @@ with
State = "" State = ""
HasVpsInterface = false HasVpsInterface = false
InterfaceAddress = None InterfaceAddress = None
SmallGroups = List<SmallGroup> () SmallGroups = ResizeArray<SmallGroup> ()
} }
/// Configure EF for this entity /// Configure EF for this entity
@ -430,9 +429,9 @@ with
} |> List.ofSeq |> ignore) } |> List.ofSeq |> ignore)
|> ignore |> ignore
mb.Model.FindEntityType(typeof<Church>).FindProperty(nameof Church.empty.Id) mb.Model.FindEntityType(typeof<Church>).FindProperty(nameof Church.empty.Id)
.SetValueConverter(Converters.ChurchIdConverter ()) .SetValueConverter (Converters.ChurchIdConverter ())
mb.Model.FindEntityType(typeof<Church>).FindProperty(nameof Church.empty.InterfaceAddress) mb.Model.FindEntityType(typeof<Church>).FindProperty(nameof Church.empty.InterfaceAddress)
.SetValueConverter(OptionConverter<string> ()) .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
@ -556,15 +555,15 @@ with
} |> List.ofSeq |> ignore) } |> List.ofSeq |> ignore)
|> ignore |> ignore
mb.Model.FindEntityType(typeof<ListPreferences>).FindProperty(nameof ListPreferences.empty.SmallGroupId) mb.Model.FindEntityType(typeof<ListPreferences>).FindProperty(nameof ListPreferences.empty.SmallGroupId)
.SetValueConverter(Converters.SmallGroupIdConverter ()) .SetValueConverter (Converters.SmallGroupIdConverter ())
mb.Model.FindEntityType(typeof<ListPreferences>).FindProperty(nameof ListPreferences.empty.RequestSort) mb.Model.FindEntityType(typeof<ListPreferences>).FindProperty(nameof ListPreferences.empty.RequestSort)
.SetValueConverter(Converters.RequestSortConverter ()) .SetValueConverter (Converters.RequestSortConverter ())
mb.Model.FindEntityType(typeof<ListPreferences>).FindProperty(nameof ListPreferences.empty.DefaultEmailType) mb.Model.FindEntityType(typeof<ListPreferences>).FindProperty(nameof ListPreferences.empty.DefaultEmailType)
.SetValueConverter(Converters.EmailFormatConverter ()) .SetValueConverter (Converters.EmailFormatConverter ())
mb.Model.FindEntityType(typeof<ListPreferences>).FindProperty(nameof ListPreferences.empty.TimeZoneId) mb.Model.FindEntityType(typeof<ListPreferences>).FindProperty(nameof ListPreferences.empty.TimeZoneId)
.SetValueConverter(Converters.TimeZoneIdConverter ()) .SetValueConverter (Converters.TimeZoneIdConverter ())
mb.Model.FindEntityType(typeof<ListPreferences>).FindProperty(nameof ListPreferences.empty.AsOfDateDisplay) mb.Model.FindEntityType(typeof<ListPreferences>).FindProperty(nameof ListPreferences.empty.AsOfDateDisplay)
.SetValueConverter(Converters.AsOfDateDisplayConverter ()) .SetValueConverter (Converters.AsOfDateDisplayConverter ())
/// A member of a small group /// A member of a small group
@ -612,11 +611,11 @@ with
} |> List.ofSeq |> ignore) } |> List.ofSeq |> ignore)
|> ignore |> ignore
mb.Model.FindEntityType(typeof<Member>).FindProperty(nameof Member.empty.Id) mb.Model.FindEntityType(typeof<Member>).FindProperty(nameof Member.empty.Id)
.SetValueConverter(Converters.MemberIdConverter ()) .SetValueConverter (Converters.MemberIdConverter ())
mb.Model.FindEntityType(typeof<Member>).FindProperty(nameof Member.empty.SmallGroupId) mb.Model.FindEntityType(typeof<Member>).FindProperty(nameof Member.empty.SmallGroupId)
.SetValueConverter(Converters.SmallGroupIdConverter ()) .SetValueConverter (Converters.SmallGroupIdConverter ())
mb.Model.FindEntityType(typeof<Member>).FindProperty(nameof Member.empty.Format) mb.Model.FindEntityType(typeof<Member>).FindProperty(nameof Member.empty.Format)
.SetValueConverter(Converters.EmailFormatOptionConverter ()) .SetValueConverter (Converters.EmailFormatOptionConverter ())
/// This represents a single prayer request /// This represents a single prayer request
@ -707,17 +706,17 @@ with
} |> List.ofSeq |> ignore) } |> List.ofSeq |> ignore)
|> ignore |> ignore
mb.Model.FindEntityType(typeof<PrayerRequest>).FindProperty(nameof PrayerRequest.empty.Id) mb.Model.FindEntityType(typeof<PrayerRequest>).FindProperty(nameof PrayerRequest.empty.Id)
.SetValueConverter(Converters.PrayerRequestIdConverter ()) .SetValueConverter (Converters.PrayerRequestIdConverter ())
mb.Model.FindEntityType(typeof<PrayerRequest>).FindProperty(nameof PrayerRequest.empty.RequestType) mb.Model.FindEntityType(typeof<PrayerRequest>).FindProperty(nameof PrayerRequest.empty.RequestType)
.SetValueConverter(Converters.PrayerRequestTypeConverter ()) .SetValueConverter (Converters.PrayerRequestTypeConverter ())
mb.Model.FindEntityType(typeof<PrayerRequest>).FindProperty(nameof PrayerRequest.empty.UserId) mb.Model.FindEntityType(typeof<PrayerRequest>).FindProperty(nameof PrayerRequest.empty.UserId)
.SetValueConverter(Converters.UserIdConverter ()) .SetValueConverter (Converters.UserIdConverter ())
mb.Model.FindEntityType(typeof<PrayerRequest>).FindProperty(nameof PrayerRequest.empty.SmallGroupId) mb.Model.FindEntityType(typeof<PrayerRequest>).FindProperty(nameof PrayerRequest.empty.SmallGroupId)
.SetValueConverter(Converters.SmallGroupIdConverter ()) .SetValueConverter (Converters.SmallGroupIdConverter ())
mb.Model.FindEntityType(typeof<PrayerRequest>).FindProperty(nameof PrayerRequest.empty.Requestor) mb.Model.FindEntityType(typeof<PrayerRequest>).FindProperty(nameof PrayerRequest.empty.Requestor)
.SetValueConverter(OptionConverter<string> ()) .SetValueConverter (OptionConverter<string> ())
mb.Model.FindEntityType(typeof<PrayerRequest>).FindProperty(nameof PrayerRequest.empty.Expiration) mb.Model.FindEntityType(typeof<PrayerRequest>).FindProperty(nameof PrayerRequest.empty.Expiration)
.SetValueConverter(Converters.ExpirationConverter ()) .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.)
@ -738,13 +737,13 @@ and [<CLIMutable; NoComparison; NoEquality>] SmallGroup =
Preferences : ListPreferences Preferences : ListPreferences
/// The members of the group /// The members of the group
Members : ICollection<Member> Members : ResizeArray<Member>
/// Prayer requests for this small group /// Prayer requests for this small group
PrayerRequests : ICollection<PrayerRequest> PrayerRequests : ResizeArray<PrayerRequest>
/// The users authorized to manage this group /// The users authorized to manage this group
Users : ICollection<UserSmallGroup> Users : ResizeArray<UserSmallGroup>
} }
with with
@ -755,9 +754,9 @@ with
Name = "" Name = ""
Church = Church.empty Church = Church.empty
Preferences = ListPreferences.empty Preferences = ListPreferences.empty
Members = List<Member> () Members = ResizeArray<Member> ()
PrayerRequests = List<PrayerRequest> () PrayerRequests = ResizeArray<PrayerRequest> ()
Users = List<UserSmallGroup> () Users = ResizeArray<UserSmallGroup> ()
} }
/// Get the local date for this group /// Get the local date for this group
@ -788,9 +787,9 @@ with
} |> List.ofSeq |> ignore) } |> List.ofSeq |> ignore)
|> ignore |> ignore
mb.Model.FindEntityType(typeof<SmallGroup>).FindProperty(nameof SmallGroup.empty.Id) mb.Model.FindEntityType(typeof<SmallGroup>).FindProperty(nameof SmallGroup.empty.Id)
.SetValueConverter(Converters.SmallGroupIdConverter ()) .SetValueConverter (Converters.SmallGroupIdConverter ())
mb.Model.FindEntityType(typeof<SmallGroup>).FindProperty(nameof SmallGroup.empty.ChurchId) mb.Model.FindEntityType(typeof<SmallGroup>).FindProperty(nameof SmallGroup.empty.ChurchId)
.SetValueConverter(Converters.ChurchIdConverter ()) .SetValueConverter (Converters.ChurchIdConverter ())
/// This represents a time zone in which a class may reside /// This represents a time zone in which a class may reside
@ -829,7 +828,7 @@ with
} |> List.ofSeq |> ignore) } |> List.ofSeq |> ignore)
|> ignore |> ignore
mb.Model.FindEntityType(typeof<TimeZone>).FindProperty(nameof TimeZone.empty.Id) mb.Model.FindEntityType(typeof<TimeZone>).FindProperty(nameof TimeZone.empty.Id)
.SetValueConverter(Converters.TimeZoneIdConverter ()) .SetValueConverter (Converters.TimeZoneIdConverter ())
/// This represents a user of PrayerTracker /// This represents a user of PrayerTracker
@ -855,8 +854,11 @@ and [<CLIMutable; NoComparison; NoEquality>] User =
/// The salt for the user's hashed password /// The salt for the user's hashed password
Salt : Guid option Salt : Guid option
/// The last time the user was seen (set whenever the user is loaded into a session)
LastSeen : DateTime option
/// The small groups which this user is authorized /// The small groups which this user is authorized
SmallGroups : ICollection<UserSmallGroup> SmallGroups : ResizeArray<UserSmallGroup>
} }
with with
@ -869,7 +871,8 @@ with
IsAdmin = false IsAdmin = false
PasswordHash = "" PasswordHash = ""
Salt = None Salt = None
SmallGroups = List<UserSmallGroup> () LastSeen = None
SmallGroups = ResizeArray<UserSmallGroup> ()
} }
/// The full name of the user /// The full name of the user
@ -889,12 +892,15 @@ with
it.Property(fun u -> u.IsAdmin).HasColumnName "is_admin" it.Property(fun u -> u.IsAdmin).HasColumnName "is_admin"
it.Property(fun u -> u.PasswordHash).HasColumnName("password_hash").IsRequired () it.Property(fun u -> u.PasswordHash).HasColumnName("password_hash").IsRequired ()
it.Property(fun u -> u.Salt).HasColumnName "salt" it.Property(fun u -> u.Salt).HasColumnName "salt"
it.Property(fun u -> u.LastSeen).HasColumnName "last_seen"
} |> List.ofSeq |> ignore) } |> List.ofSeq |> ignore)
|> ignore |> ignore
mb.Model.FindEntityType(typeof<User>).FindProperty(nameof User.empty.Id) mb.Model.FindEntityType(typeof<User>).FindProperty(nameof User.empty.Id)
.SetValueConverter(Converters.UserIdConverter ()) .SetValueConverter (Converters.UserIdConverter ())
mb.Model.FindEntityType(typeof<User>).FindProperty(nameof User.empty.Salt) mb.Model.FindEntityType(typeof<User>).FindProperty(nameof User.empty.Salt)
.SetValueConverter(OptionConverter<Guid> ()) .SetValueConverter (OptionConverter<Guid> ())
mb.Model.FindEntityType(typeof<User>).FindProperty(nameof User.empty.LastSeen)
.SetValueConverter (OptionConverter<DateTime> ())
/// Cross-reference between user and small group /// Cross-reference between user and small group
@ -930,15 +936,15 @@ with
it.Property(fun usg -> usg.UserId).HasColumnName "user_id" it.Property(fun usg -> usg.UserId).HasColumnName "user_id"
it.Property(fun usg -> usg.SmallGroupId).HasColumnName "small_group_id" it.Property(fun usg -> usg.SmallGroupId).HasColumnName "small_group_id"
it.HasOne(fun usg -> usg.User) it.HasOne(fun usg -> usg.User)
.WithMany(fun u -> u.SmallGroups :> IEnumerable<UserSmallGroup>) .WithMany(fun u -> u.SmallGroups :> seq<UserSmallGroup>)
.HasForeignKey(fun usg -> usg.UserId :> obj) .HasForeignKey(fun usg -> usg.UserId :> obj)
it.HasOne(fun usg -> usg.SmallGroup) it.HasOne(fun usg -> usg.SmallGroup)
.WithMany(fun sg -> sg.Users :> IEnumerable<UserSmallGroup>) .WithMany(fun sg -> sg.Users :> seq<UserSmallGroup>)
.HasForeignKey(fun usg -> usg.SmallGroupId :> obj) .HasForeignKey(fun usg -> usg.SmallGroupId :> obj)
} |> List.ofSeq |> ignore) } |> List.ofSeq |> ignore)
|> ignore |> ignore
mb.Model.FindEntityType(typeof<UserSmallGroup>).FindProperty(nameof UserSmallGroup.empty.UserId) mb.Model.FindEntityType(typeof<UserSmallGroup>).FindProperty(nameof UserSmallGroup.empty.UserId)
.SetValueConverter(Converters.UserIdConverter ()) .SetValueConverter (Converters.UserIdConverter ())
mb.Model.FindEntityType(typeof<UserSmallGroup>).FindProperty(nameof UserSmallGroup.empty.SmallGroupId) mb.Model.FindEntityType(typeof<UserSmallGroup>).FindProperty(nameof UserSmallGroup.empty.SmallGroupId)
.SetValueConverter(Converters.SmallGroupIdConverter ()) .SetValueConverter (Converters.SmallGroupIdConverter ())

View File

@ -48,13 +48,14 @@ type InitialDatabase () =
name = "pt_user", name = "pt_user",
schema = "pt", schema = "pt",
columns = (fun table -> columns = (fun table ->
{| Id = table.Column<Guid> (name = "id", nullable = false) {| Id = table.Column<Guid> (name = "id", nullable = false)
Email = table.Column<string> (name = "email", nullable = false) Email = table.Column<string> (name = "email", nullable = false)
FirstName = table.Column<string> (name = "first_name", nullable = false) FirstName = table.Column<string> (name = "first_name", nullable = false)
IsAdmin = table.Column<bool> (name = "is_admin", nullable = false) IsAdmin = table.Column<bool> (name = "is_admin", nullable = false)
LastName = table.Column<string> (name = "last_name", nullable = false) LastName = table.Column<string> (name = "last_name", nullable = false)
PasswordHash = table.Column<string> (name = "password_hash", nullable = false) PasswordHash = table.Column<string> (name = "password_hash", nullable = false)
Salt = table.Column<Guid> (name = "salt", nullable = true) Salt = table.Column<Guid> (name = "salt", nullable = true)
LastSeen = table.Column<DateTime> (name = "last_seen", nullable = true)
|}), |}),
constraints = fun table -> constraints = fun table ->
table.PrimaryKey("pk_pt_user", fun x -> upcast x.Id) |> ignore) table.PrimaryKey("pk_pt_user", fun x -> upcast x.Id) |> ignore)
@ -321,6 +322,7 @@ type InitialDatabase () =
b.Property<string>("LastName").HasColumnName("last_name").IsRequired() |> ignore b.Property<string>("LastName").HasColumnName("last_name").IsRequired() |> ignore
b.Property<string>("PasswordHash").HasColumnName("password_hash").IsRequired() |> ignore b.Property<string>("PasswordHash").HasColumnName("password_hash").IsRequired() |> ignore
b.Property<Guid>("Salt").HasColumnName("salt") |> ignore b.Property<Guid>("Salt").HasColumnName("salt") |> ignore
b.Property<DateTime>("LastSeen").HasColumnName("last_seen") |> ignore
b.HasKey("Id") |> ignore b.HasKey("Id") |> ignore
b.ToTable("pt_user") |> ignore) b.ToTable("pt_user") |> ignore)
|> ignore |> ignore

View File

@ -106,6 +106,7 @@ type AppDbContextModelSnapshot () =
b.Property<string>("LastName").HasColumnName("last_name").IsRequired() |> ignore b.Property<string>("LastName").HasColumnName("last_name").IsRequired() |> ignore
b.Property<string>("PasswordHash").HasColumnName("password_hash").IsRequired() |> ignore b.Property<string>("PasswordHash").HasColumnName("password_hash").IsRequired() |> ignore
b.Property<Guid>("Salt").HasColumnName("salt") |> ignore b.Property<Guid>("Salt").HasColumnName("salt") |> ignore
b.Property<DateTime>("LastSeen").HasColumnName("last_seen") |> ignore
b.HasKey("Id") |> ignore b.HasKey("Id") |> ignore
b.ToTable("pt_user") |> ignore) b.ToTable("pt_user") |> ignore)
|> ignore |> ignore

View File

@ -825,4 +825,7 @@
<data name="State or Province" xml:space="preserve"> <data name="State or Province" xml:space="preserve">
<value>Estado o Provincia</value> <value>Estado o Provincia</value>
</data> </data>
<data name="Last Seen" xml:space="preserve">
<value>Ultima vez Visto</value>
</data>
</root> </root>

View File

@ -192,7 +192,7 @@ let maintain (users : User list) ctx viewInfo =
| [] -> space | [] -> space
| _ -> | _ ->
table [ _class "pt-table pt-action-table" ] [ table [ _class "pt-table pt-action-table" ] [
tableHeadings s [ "Actions"; "Name"; "Admin?" ] tableHeadings s [ "Actions"; "Name"; "Last Seen"; "Admin?" ]
users users
|> List.map (fun user -> |> List.map (fun user ->
let userId = shortGuid user.Id.Value let userId = shortGuid user.Id.Value
@ -212,6 +212,9 @@ let maintain (users : User list) ctx viewInfo =
] ]
] ]
td [] [ str user.Name ] td [] [ str user.Name ]
td [] [
str (match user.LastSeen with Some dt -> dt.ToString s["MMMM d, yyyy"] | None -> "--")
]
td [ _class "pt-center-text" ] [ td [ _class "pt-center-text" ] [
if user.IsAdmin then strong [] [ locStr s["Yes"] ] else locStr s["No"] if user.IsAdmin then strong [] [ locStr s["Yes"] ] else locStr s["No"]
] ]

View File

@ -1,6 +1,7 @@
[<AutoOpen>] [<AutoOpen>]
module PrayerTracker.Extensions module PrayerTracker.Extensions
open System
open Microsoft.AspNetCore.Http open Microsoft.AspNetCore.Http
open Microsoft.FSharpLu open Microsoft.FSharpLu
open Newtonsoft.Json open Newtonsoft.Json
@ -98,7 +99,11 @@ type HttpContext with
| Some userId -> | Some userId ->
match! this.Db.TryUserById userId with match! this.Db.TryUserById userId with
| Some user -> | Some user ->
this.Session.CurrentUser <- Some user // Set last seen for user
this.Db.UpdateEntry { user with LastSeen = Some DateTime.UtcNow }
let! _ = this.Db.SaveChangesAsync ()
this.Session.CurrentUser <-
Some { user with PasswordHash = ""; SmallGroups = ResizeArray<UserSmallGroup> () }
return Some user return Some user
| None -> return None | None -> return None
| None -> return None | None -> return None

View File

@ -94,6 +94,7 @@ ALTER TABLE pt."User" RENAME COLUMN "PasswordHash" TO password_hash;
ALTER TABLE pt."User" RENAME COLUMN "Salt" TO salt; ALTER TABLE pt."User" RENAME COLUMN "Salt" TO salt;
ALTER TABLE pt."User" RENAME CONSTRAINT "PK_User" TO pk_pt_user; ALTER TABLE pt."User" RENAME CONSTRAINT "PK_User" TO pk_pt_user;
ALTER TABLE pt."User" RENAME TO pt_user; ALTER TABLE pt."User" RENAME TO pt_user;
ALTER TABLE pt.pt_user ADD COLUMN last_seen timestamp;
-- User / Small Group -- User / Small Group
ALTER TABLE pt."User_SmallGroup" RENAME COLUMN "UserId" TO user_id; ALTER TABLE pt."User_SmallGroup" RENAME COLUMN "UserId" TO user_id;