WIP on SQL migration
This commit is contained in:
parent
eb947a48af
commit
37dcf41c98
214
src/PrayerTracker.Data/Access.fs
Normal file
214
src/PrayerTracker.Data/Access.fs
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
namespace PrayerTracker.Data
|
||||||
|
|
||||||
|
open NodaTime
|
||||||
|
open Npgsql
|
||||||
|
open Npgsql.FSharp
|
||||||
|
open PrayerTracker.Entities
|
||||||
|
|
||||||
|
/// Helper functions for the PostgreSQL data implementation
|
||||||
|
[<AutoOpen>]
|
||||||
|
module private Helpers =
|
||||||
|
|
||||||
|
/// Map a row to a Church instance
|
||||||
|
let mapToChurch (row : RowReader) =
|
||||||
|
{ Id = ChurchId (row.uuid "id")
|
||||||
|
Name = row.string "name"
|
||||||
|
City = row.string "city"
|
||||||
|
State = row.string "state"
|
||||||
|
HasVpsInterface = row.bool "has_vps_interface"
|
||||||
|
InterfaceAddress = row.stringOrNone "interface_address"
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Map a row to a ListPreferences instance
|
||||||
|
let mapToListPreferences (row : RowReader) =
|
||||||
|
{ SmallGroupId = SmallGroupId (row.uuid "small_group_id")
|
||||||
|
DaysToKeepNew = row.int "days_to_keep_new"
|
||||||
|
DaysToExpire = row.int "days_to_expire"
|
||||||
|
LongTermUpdateWeeks = row.int "long_term_update_weeks"
|
||||||
|
EmailFromName = row.string "email_from_name"
|
||||||
|
EmailFromAddress = row.string "email_from_address"
|
||||||
|
Fonts = row.string "fonts"
|
||||||
|
HeadingColor = row.string "heading_color"
|
||||||
|
LineColor = row.string "line_color"
|
||||||
|
HeadingFontSize = row.int "heading_font_size"
|
||||||
|
TextFontSize = row.int "text_font_size"
|
||||||
|
RequestSort = RequestSort.fromCode (row.string "request_sort")
|
||||||
|
GroupPassword = row.string "group_password"
|
||||||
|
DefaultEmailType = EmailFormat.fromCode (row.string "default_email_type")
|
||||||
|
IsPublic = row.bool "is_public"
|
||||||
|
TimeZoneId = TimeZoneId (row.string "time_zone_id")
|
||||||
|
PageSize = row.int "page_size"
|
||||||
|
AsOfDateDisplay = AsOfDateDisplay.fromCode (row.string "as_of_date_display")
|
||||||
|
TimeZone = TimeZone.empty
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Map a row to a Small Group instance
|
||||||
|
let mapToSmallGroup (row : RowReader) =
|
||||||
|
{ Id = SmallGroupId (row.uuid "id")
|
||||||
|
ChurchId = ChurchId (row.uuid "church_id")
|
||||||
|
Name = row.string "group_name"
|
||||||
|
Preferences = ListPreferences.empty
|
||||||
|
Church = Church.empty
|
||||||
|
Members = ResizeArray ()
|
||||||
|
PrayerRequests = ResizeArray ()
|
||||||
|
Users = ResizeArray ()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Map a row to a Small Group instance with populated list preferences
|
||||||
|
let mapToSmallGroupWithPreferences (row : RowReader) =
|
||||||
|
{ mapToSmallGroup row with
|
||||||
|
Preferences = mapToListPreferences row
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Map a row to a User instance
|
||||||
|
let mapToUser (row : RowReader) =
|
||||||
|
{ Id = UserId (row.uuid "id")
|
||||||
|
FirstName = row.string "first_name"
|
||||||
|
LastName = row.string "last_name"
|
||||||
|
Email = row.string "email"
|
||||||
|
IsAdmin = row.bool "is_admin"
|
||||||
|
PasswordHash = row.string "password_hash"
|
||||||
|
Salt = None
|
||||||
|
LastSeen = row.fieldValueOrNone<Instant> "last_seen"
|
||||||
|
SmallGroups = ResizeArray ()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module Churches =
|
||||||
|
let tryById (churchId : ChurchId) conn = backgroundTask {
|
||||||
|
let! church =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query "SELECT * FROM pt.church WHERE id = @id"
|
||||||
|
|> Sql.parameters [ "@id", Sql.uuid churchId.Value ]
|
||||||
|
|> Sql.executeAsync mapToChurch
|
||||||
|
return List.tryHead church
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Functions to retrieve small group information
|
||||||
|
module SmallGroups =
|
||||||
|
|
||||||
|
/// Get a list of small group IDs along with a description that includes the church name
|
||||||
|
let listAll conn =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query """
|
||||||
|
SELECT g.group_name, g.id, c.church_name
|
||||||
|
FROM pt.small_group g
|
||||||
|
INNER JOIN pt.church c ON c.id = g.church_id
|
||||||
|
ORDER BY c.church_name, g.group_name"""
|
||||||
|
|> Sql.executeAsync (fun row ->
|
||||||
|
Giraffe.ShortGuid.fromGuid (row.uuid "id"), $"""{row.string "church_name"} | {row.string "group_name"}""")
|
||||||
|
|
||||||
|
let tryByIdWithPreferences (groupId : SmallGroupId) conn = backgroundTask {
|
||||||
|
let! group =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query """
|
||||||
|
SELECT sg.*, lp.*
|
||||||
|
FROM pt.small_group sg
|
||||||
|
INNER JOIN list_preference lp ON lp.small_group_id = sg.id
|
||||||
|
WHERE sg.id = @id"""
|
||||||
|
|> Sql.parameters [ "@id", Sql.uuid groupId.Value ]
|
||||||
|
|> Sql.executeAsync mapToSmallGroupWithPreferences
|
||||||
|
return List.tryHead group
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Functions to manipulate users
|
||||||
|
module Users =
|
||||||
|
|
||||||
|
/// Retrieve all PrayerTracker users
|
||||||
|
let all conn =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query "SELECT * FROM pt.pt_user ORDER BY last_name, first_name"
|
||||||
|
|> Sql.executeAsync mapToUser
|
||||||
|
|
||||||
|
/// Delete a user by its database ID
|
||||||
|
let deleteById (userId : UserId) conn = backgroundTask {
|
||||||
|
let! _ =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query "DELETE FROM pt.pt_user WHERE id = @id"
|
||||||
|
|> Sql.parameters [ "@id", Sql.uuid userId.Value ]
|
||||||
|
|> Sql.executeNonQueryAsync
|
||||||
|
return ()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save a user's information
|
||||||
|
let save user conn = backgroundTask {
|
||||||
|
let! _ =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query """
|
||||||
|
INSERT INTO pt.pt_user (
|
||||||
|
id, first_name, last_name, email, is_admin, password_hash
|
||||||
|
) VALUES (
|
||||||
|
@id, @firstName, @lastName, @email, @isAdmin, @passwordHash
|
||||||
|
) ON CONFLICT (id) DO UPDATE
|
||||||
|
SET first_name = EXCLUDED.first_name,
|
||||||
|
last_name = EXCLUDED.last_name,
|
||||||
|
email = EXCLUDED.email,
|
||||||
|
is_admin = EXCLUDED.is_admin,
|
||||||
|
password_hash = EXCLUDED.password_hash"""
|
||||||
|
|> Sql.parameters
|
||||||
|
[ "@id", Sql.uuid user.Id.Value
|
||||||
|
"@firstName", Sql.string user.FirstName
|
||||||
|
"@lastName", Sql.string user.LastName
|
||||||
|
"@email", Sql.string user.Email
|
||||||
|
"@isAdmin", Sql.bool user.IsAdmin
|
||||||
|
"@passwordHash", Sql.string user.PasswordHash
|
||||||
|
]
|
||||||
|
|> Sql.executeNonQueryAsync
|
||||||
|
return ()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find a user by its e-mail address and authorized small group
|
||||||
|
let tryByEmailAndGroup email (groupId : SmallGroupId) conn = backgroundTask {
|
||||||
|
let! user =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query """
|
||||||
|
SELECT u.*
|
||||||
|
FROM pt.pt_user u
|
||||||
|
INNER JOIN pt.user_small_group usg ON usg.user_id = u.id AND usg.small_group_id = @groupId
|
||||||
|
WHERE u.email = @email"""
|
||||||
|
|> Sql.parameters [ "@email", Sql.string email; "@groupId", Sql.uuid groupId.Value ]
|
||||||
|
|> Sql.executeAsync mapToUser
|
||||||
|
return List.tryHead user
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find a user by their database ID
|
||||||
|
let tryById (userId : UserId) conn = backgroundTask {
|
||||||
|
let! user =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query "SELECT * FROM pt.pt_user WHERE id = @id"
|
||||||
|
|> Sql.parameters [ "@id", Sql.uuid userId.Value ]
|
||||||
|
|> Sql.executeAsync mapToUser
|
||||||
|
return List.tryHead user
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update a user's last seen date/time
|
||||||
|
let updateLastSeen (userId : UserId) (now : Instant) conn = backgroundTask {
|
||||||
|
let! _ =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query "UPDATE pt.pt_user SET last_seen = @now WHERE id = @id"
|
||||||
|
|> Sql.parameters [ "@id", Sql.uuid userId.Value; "@now", Sql.parameter (NpgsqlParameter ("@now", now)) ]
|
||||||
|
|> Sql.executeNonQueryAsync
|
||||||
|
return ()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update a user's password hash
|
||||||
|
let updatePassword user conn = backgroundTask {
|
||||||
|
let! _ =
|
||||||
|
conn
|
||||||
|
|> Sql.existingConnection
|
||||||
|
|> Sql.query "UPDATE pt.pt_user SET password_hash = @passwordHash WHERE id = @id"
|
||||||
|
|> Sql.parameters [ "@id", Sql.uuid user.Id.Value; "@passwordHash", Sql.string user.PasswordHash ]
|
||||||
|
|> Sql.executeNonQueryAsync
|
||||||
|
return ()
|
||||||
|
}
|
|
@ -396,9 +396,6 @@ type [<CLIMutable; NoComparison; NoEquality>] Church =
|
||||||
|
|
||||||
/// The address for the interface
|
/// The address for the interface
|
||||||
InterfaceAddress : string option
|
InterfaceAddress : string option
|
||||||
|
|
||||||
/// Small groups for this church
|
|
||||||
SmallGroups : ResizeArray<SmallGroup>
|
|
||||||
}
|
}
|
||||||
with
|
with
|
||||||
/// An empty church
|
/// An empty church
|
||||||
|
@ -410,7 +407,6 @@ with
|
||||||
State = ""
|
State = ""
|
||||||
HasVpsInterface = false
|
HasVpsInterface = false
|
||||||
InterfaceAddress = None
|
InterfaceAddress = None
|
||||||
SmallGroups = ResizeArray<SmallGroup> ()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configure EF for this entity
|
/// Configure EF for this entity
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Entities.fs" />
|
<Compile Include="Entities.fs" />
|
||||||
|
<Compile Include="Access.fs" />
|
||||||
<Compile Include="AppDbContext.fs" />
|
<Compile Include="AppDbContext.fs" />
|
||||||
<Compile Include="DataAccess.fs" />
|
<Compile Include="DataAccess.fs" />
|
||||||
<Compile Include="Migrations\20161217153124_InitialDatabase.fs" />
|
<Compile Include="Migrations\20161217153124_InitialDatabase.fs" />
|
||||||
|
@ -19,6 +20,8 @@
|
||||||
<PackageReference Include="NodaTime" Version="3.1.0" />
|
<PackageReference Include="NodaTime" Version="3.1.0" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.6" />
|
<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.NodaTime" Version="6.0.6" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -43,8 +43,6 @@ let churchTests =
|
||||||
Expect.equal mt.State "" "The state should have been blank"
|
Expect.equal mt.State "" "The state should have been blank"
|
||||||
Expect.isFalse mt.HasVpsInterface "The church should not show that it has an interface"
|
Expect.isFalse mt.HasVpsInterface "The church should not show that it has an interface"
|
||||||
Expect.isNone mt.InterfaceAddress "The interface address should not exist"
|
Expect.isNone mt.InterfaceAddress "The interface address should not exist"
|
||||||
Expect.isNotNull mt.SmallGroups "The small groups navigation property should not be null"
|
|
||||||
Expect.isEmpty mt.SmallGroups "There should be no small groups for an empty church"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -71,7 +71,6 @@ module Configure =
|
||||||
let _ = svc.AddSingleton<IClock> SystemClock.Instance
|
let _ = svc.AddSingleton<IClock> SystemClock.Instance
|
||||||
|
|
||||||
let config = svc.BuildServiceProvider().GetRequiredService<IConfiguration> ()
|
let config = svc.BuildServiceProvider().GetRequiredService<IConfiguration> ()
|
||||||
//NpgsqlConnection.GlobalTypeMapper.
|
|
||||||
let _ =
|
let _ =
|
||||||
svc.AddDbContext<AppDbContext> (
|
svc.AddDbContext<AppDbContext> (
|
||||||
(fun options ->
|
(fun options ->
|
||||||
|
|
|
@ -15,8 +15,9 @@ let private findStats (db : AppDbContext) churchId = task {
|
||||||
|
|
||||||
/// POST /church/[church-id]/delete
|
/// POST /church/[church-id]/delete
|
||||||
let delete chId : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next ctx -> task {
|
let delete chId : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next ctx -> task {
|
||||||
let churchId = ChurchId chId
|
let churchId = ChurchId chId
|
||||||
match! ctx.Db.TryChurchById churchId with
|
use! conn = ctx.Conn
|
||||||
|
match! Data.Churches.tryById churchId conn with
|
||||||
| Some church ->
|
| Some church ->
|
||||||
let! _, stats = findStats ctx.Db churchId
|
let! _, stats = findStats ctx.Db churchId
|
||||||
ctx.Db.RemoveEntry church
|
ctx.Db.RemoveEntry church
|
||||||
|
@ -39,7 +40,8 @@ let edit churchId : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> ta
|
||||||
|> Views.Church.edit EditChurch.empty ctx
|
|> Views.Church.edit EditChurch.empty ctx
|
||||||
|> renderHtml next ctx
|
|> renderHtml next ctx
|
||||||
else
|
else
|
||||||
match! ctx.Db.TryChurchById (ChurchId churchId) with
|
use! conn = ctx.Conn
|
||||||
|
match! Data.Churches.tryById (ChurchId churchId) conn with
|
||||||
| Some church ->
|
| Some church ->
|
||||||
return!
|
return!
|
||||||
viewInfo ctx
|
viewInfo ctx
|
||||||
|
@ -65,9 +67,10 @@ open System.Threading.Tasks
|
||||||
let save : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next ctx -> task {
|
let save : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next ctx -> task {
|
||||||
match! ctx.TryBindFormAsync<EditChurch> () with
|
match! ctx.TryBindFormAsync<EditChurch> () with
|
||||||
| Ok model ->
|
| Ok model ->
|
||||||
|
let! conn = ctx.Conn
|
||||||
let! church =
|
let! church =
|
||||||
if model.IsNew then Task.FromResult (Some { Church.empty with Id = (Guid.NewGuid >> ChurchId) () })
|
if model.IsNew then Task.FromResult (Some { Church.empty with Id = (Guid.NewGuid >> ChurchId) () })
|
||||||
else ctx.Db.TryChurchById (idFromShort ChurchId model.ChurchId)
|
else Data.Churches.tryById (idFromShort ChurchId model.ChurchId) conn
|
||||||
match church with
|
match church with
|
||||||
| Some ch ->
|
| Some ch ->
|
||||||
model.PopulateChurch ch
|
model.PopulateChurch ch
|
||||||
|
|
|
@ -69,15 +69,31 @@ type ClaimsPrincipal with
|
||||||
else None
|
else None
|
||||||
|
|
||||||
|
|
||||||
|
open System.Threading.Tasks
|
||||||
open Giraffe
|
open Giraffe
|
||||||
|
open Microsoft.Extensions.Configuration
|
||||||
|
open Npgsql
|
||||||
open PrayerTracker
|
open PrayerTracker
|
||||||
|
|
||||||
/// Extensions on the ASP.NET Core HTTP context
|
/// Extensions on the ASP.NET Core HTTP context
|
||||||
type HttpContext with
|
type HttpContext with
|
||||||
|
|
||||||
|
// TODO: is this disposed?
|
||||||
|
member private this.LazyConn : Lazy<Task<NpgsqlConnection>> = lazy (backgroundTask {
|
||||||
|
let cfg = this.GetService<IConfiguration> ()
|
||||||
|
let conn = new NpgsqlConnection (cfg.GetConnectionString "PrayerTracker")
|
||||||
|
do! conn.OpenAsync ()
|
||||||
|
return conn
|
||||||
|
})
|
||||||
|
|
||||||
/// The EF Core database context (via DI)
|
/// The EF Core database context (via DI)
|
||||||
member this.Db = this.GetService<AppDbContext> ()
|
member this.Db = this.GetService<AppDbContext> ()
|
||||||
|
|
||||||
|
/// The PostgreSQL connection (configured via DI)
|
||||||
|
member this.Conn = backgroundTask {
|
||||||
|
return! this.LazyConn.Force ()
|
||||||
|
}
|
||||||
|
|
||||||
/// The system clock (via DI)
|
/// The system clock (via DI)
|
||||||
member this.Clock = this.GetService<IClock> ()
|
member this.Clock = this.GetService<IClock> ()
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ open Giraffe
|
||||||
open Microsoft.AspNetCore.Http
|
open Microsoft.AspNetCore.Http
|
||||||
open Microsoft.AspNetCore.Identity
|
open Microsoft.AspNetCore.Identity
|
||||||
open PrayerTracker
|
open PrayerTracker
|
||||||
|
open PrayerTracker.Data
|
||||||
open PrayerTracker.Entities
|
open PrayerTracker.Entities
|
||||||
open PrayerTracker.ViewModels
|
open PrayerTracker.ViewModels
|
||||||
|
|
||||||
|
@ -52,16 +53,15 @@ module Hashing =
|
||||||
|
|
||||||
|
|
||||||
/// Retrieve a user from the database by password, upgrading password hashes if required
|
/// Retrieve a user from the database by password, upgrading password hashes if required
|
||||||
let private findUserByPassword model (db : AppDbContext) = task {
|
let private findUserByPassword model conn = task {
|
||||||
match! db.TryUserByEmailAndGroup model.Email (idFromShort SmallGroupId model.SmallGroupId) with
|
match! Users.tryByEmailAndGroup model.Email (idFromShort SmallGroupId model.SmallGroupId) conn with
|
||||||
| Some user ->
|
| Some user ->
|
||||||
let hasher = PrayerTrackerPasswordHasher ()
|
let hasher = PrayerTrackerPasswordHasher ()
|
||||||
match hasher.VerifyHashedPassword (user, user.PasswordHash, model.Password) with
|
match hasher.VerifyHashedPassword (user, user.PasswordHash, model.Password) with
|
||||||
| PasswordVerificationResult.Success -> return Some user
|
| PasswordVerificationResult.Success -> return Some user
|
||||||
| PasswordVerificationResult.SuccessRehashNeeded ->
|
| PasswordVerificationResult.SuccessRehashNeeded ->
|
||||||
let upgraded = { user with PasswordHash = hasher.HashPassword (user, model.Password) }
|
let upgraded = { user with PasswordHash = hasher.HashPassword (user, model.Password) }
|
||||||
db.UpdateEntry upgraded
|
do! Users.updatePassword upgraded conn
|
||||||
let! _ = db.SaveChangesAsync ()
|
|
||||||
return Some upgraded
|
return Some upgraded
|
||||||
| _ -> return None
|
| _ -> return None
|
||||||
| None -> return None
|
| None -> return None
|
||||||
|
@ -81,8 +81,9 @@ let changePassword : HttpHandler = requireAccess [ User ] >=> validateCsrf >=> f
|
||||||
let s = Views.I18N.localizer.Force ()
|
let s = Views.I18N.localizer.Force ()
|
||||||
let curUsr = ctx.Session.CurrentUser.Value
|
let curUsr = ctx.Session.CurrentUser.Value
|
||||||
let hasher = PrayerTrackerPasswordHasher ()
|
let hasher = PrayerTrackerPasswordHasher ()
|
||||||
|
let! conn = ctx.Conn
|
||||||
let! user = task {
|
let! user = task {
|
||||||
match! ctx.Db.TryUserById curUsr.Id with
|
match! Users.tryById curUsr.Id conn with
|
||||||
| Some usr ->
|
| Some usr ->
|
||||||
if hasher.VerifyHashedPassword (usr, usr.PasswordHash, model.OldPassword)
|
if hasher.VerifyHashedPassword (usr, usr.PasswordHash, model.OldPassword)
|
||||||
= PasswordVerificationResult.Success then
|
= PasswordVerificationResult.Success then
|
||||||
|
@ -92,8 +93,7 @@ let changePassword : HttpHandler = requireAccess [ User ] >=> validateCsrf >=> f
|
||||||
}
|
}
|
||||||
match user with
|
match user with
|
||||||
| Some usr when model.NewPassword = model.NewPasswordConfirm ->
|
| Some usr when model.NewPassword = model.NewPasswordConfirm ->
|
||||||
ctx.Db.UpdateEntry { usr with PasswordHash = hasher.HashPassword (usr, model.NewPassword) }
|
do! Users.updatePassword { usr with PasswordHash = hasher.HashPassword (usr, model.NewPassword) } conn
|
||||||
let! _ = ctx.Db.SaveChangesAsync ()
|
|
||||||
addInfo ctx s["Your password was changed successfully"]
|
addInfo ctx s["Your password was changed successfully"]
|
||||||
return! redirectTo false "/" next ctx
|
return! redirectTo false "/" next ctx
|
||||||
| Some _ ->
|
| Some _ ->
|
||||||
|
@ -107,12 +107,12 @@ let changePassword : HttpHandler = requireAccess [ User ] >=> validateCsrf >=> f
|
||||||
|
|
||||||
/// POST /user/[user-id]/delete
|
/// POST /user/[user-id]/delete
|
||||||
let delete usrId : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next ctx -> task {
|
let delete usrId : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next ctx -> task {
|
||||||
let userId = UserId usrId
|
let userId = UserId usrId
|
||||||
match! ctx.Db.TryUserById userId with
|
let! conn = ctx.Conn
|
||||||
|
match! Users.tryById userId conn with
|
||||||
| Some user ->
|
| Some user ->
|
||||||
ctx.Db.RemoveEntry user
|
do! Users.deleteById userId conn
|
||||||
let! _ = ctx.Db.SaveChangesAsync ()
|
let s = Views.I18N.localizer.Force ()
|
||||||
let s = Views.I18N.localizer.Force ()
|
|
||||||
addInfo ctx s["Successfully deleted user {0}", user.Name]
|
addInfo ctx s["Successfully deleted user {0}", user.Name]
|
||||||
return! redirectTo false "/users" next ctx
|
return! redirectTo false "/users" next ctx
|
||||||
| _ -> return! fourOhFour ctx
|
| _ -> return! fourOhFour ctx
|
||||||
|
@ -128,10 +128,11 @@ open Microsoft.AspNetCore.Html
|
||||||
let doLogOn : HttpHandler = requireAccess [ AccessLevel.Public ] >=> validateCsrf >=> fun next ctx -> task {
|
let doLogOn : HttpHandler = requireAccess [ AccessLevel.Public ] >=> validateCsrf >=> fun next ctx -> task {
|
||||||
match! ctx.TryBindFormAsync<UserLogOn> () with
|
match! ctx.TryBindFormAsync<UserLogOn> () with
|
||||||
| Ok model ->
|
| Ok model ->
|
||||||
let s = Views.I18N.localizer.Force ()
|
let s = Views.I18N.localizer.Force ()
|
||||||
match! findUserByPassword model ctx.Db with
|
let! conn = ctx.Conn
|
||||||
|
match! findUserByPassword model conn with
|
||||||
| Some user ->
|
| Some user ->
|
||||||
match! ctx.Db.TryGroupById (idFromShort SmallGroupId model.SmallGroupId) with
|
match! SmallGroups.tryByIdWithPreferences (idFromShort SmallGroupId model.SmallGroupId) conn with
|
||||||
| Some group ->
|
| Some group ->
|
||||||
ctx.Session.CurrentUser <- Some user
|
ctx.Session.CurrentUser <- Some user
|
||||||
ctx.Session.CurrentGroup <- Some group
|
ctx.Session.CurrentGroup <- Some group
|
||||||
|
@ -145,8 +146,7 @@ let doLogOn : HttpHandler = requireAccess [ AccessLevel.Public ] >=> validateCsr
|
||||||
AuthenticationProperties (
|
AuthenticationProperties (
|
||||||
IssuedUtc = DateTimeOffset.UtcNow,
|
IssuedUtc = DateTimeOffset.UtcNow,
|
||||||
IsPersistent = defaultArg model.RememberMe false))
|
IsPersistent = defaultArg model.RememberMe false))
|
||||||
ctx.Db.UpdateEntry { user with LastSeen = Some ctx.Now }
|
do! Users.updateLastSeen user.Id ctx.Now conn
|
||||||
let! _ = ctx.Db.SaveChangesAsync ()
|
|
||||||
addHtmlInfo ctx s["Log On Successful • Welcome to {0}", s["PrayerTracker"]]
|
addHtmlInfo ctx s["Log On Successful • Welcome to {0}", s["PrayerTracker"]]
|
||||||
return! redirectTo false (sanitizeUrl model.RedirectUrl "/small-group") next ctx
|
return! redirectTo false (sanitizeUrl model.RedirectUrl "/small-group") next ctx
|
||||||
| None -> return! fourOhFour ctx
|
| None -> return! fourOhFour ctx
|
||||||
|
@ -177,7 +177,8 @@ let edit usrId : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> task
|
||||||
|> Views.User.edit EditUser.empty ctx
|
|> Views.User.edit EditUser.empty ctx
|
||||||
|> renderHtml next ctx
|
|> renderHtml next ctx
|
||||||
else
|
else
|
||||||
match! ctx.Db.TryUserById userId with
|
let! conn = ctx.Conn
|
||||||
|
match! Users.tryById userId conn with
|
||||||
| Some user ->
|
| Some user ->
|
||||||
return!
|
return!
|
||||||
viewInfo ctx
|
viewInfo ctx
|
||||||
|
@ -189,7 +190,8 @@ let edit usrId : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> task
|
||||||
/// GET /user/log-on
|
/// GET /user/log-on
|
||||||
let logOn : HttpHandler = requireAccess [ AccessLevel.Public ] >=> fun next ctx -> task {
|
let logOn : HttpHandler = requireAccess [ AccessLevel.Public ] >=> fun next ctx -> task {
|
||||||
let s = Views.I18N.localizer.Force ()
|
let s = Views.I18N.localizer.Force ()
|
||||||
let! groups = ctx.Db.GroupList ()
|
let! conn = ctx.Conn
|
||||||
|
let! groups = SmallGroups.listAll conn
|
||||||
let url = Option.ofObj <| ctx.Session.GetString Key.Session.redirectUrl
|
let url = Option.ofObj <| ctx.Session.GetString Key.Session.redirectUrl
|
||||||
match url with
|
match url with
|
||||||
| Some _ ->
|
| Some _ ->
|
||||||
|
@ -204,7 +206,8 @@ let logOn : HttpHandler = requireAccess [ AccessLevel.Public ] >=> fun next ctx
|
||||||
|
|
||||||
/// GET /users
|
/// GET /users
|
||||||
let maintain : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> task {
|
let maintain : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> task {
|
||||||
let! users = ctx.Db.AllUsers ()
|
let! conn = ctx.Conn
|
||||||
|
let! users = Users.all conn
|
||||||
return!
|
return!
|
||||||
viewInfo ctx
|
viewInfo ctx
|
||||||
|> Views.User.maintain users ctx
|
|> Views.User.maintain users ctx
|
||||||
|
@ -223,16 +226,16 @@ open System.Threading.Tasks
|
||||||
let save : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next ctx -> task {
|
let save : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next ctx -> task {
|
||||||
match! ctx.TryBindFormAsync<EditUser> () with
|
match! ctx.TryBindFormAsync<EditUser> () with
|
||||||
| Ok model ->
|
| Ok model ->
|
||||||
|
let! conn = ctx.Conn
|
||||||
let! user =
|
let! user =
|
||||||
if model.IsNew then Task.FromResult (Some { User.empty with Id = (Guid.NewGuid >> UserId) () })
|
if model.IsNew then Task.FromResult (Some { User.empty with Id = (Guid.NewGuid >> UserId) () })
|
||||||
else ctx.Db.TryUserById (idFromShort UserId model.UserId)
|
else Users.tryById (idFromShort UserId model.UserId) conn
|
||||||
match user with
|
match user with
|
||||||
| Some usr ->
|
| Some usr ->
|
||||||
let hasher = PrayerTrackerPasswordHasher ()
|
let hasher = PrayerTrackerPasswordHasher ()
|
||||||
let updatedUser = model.PopulateUser usr (fun pw -> hasher.HashPassword (usr, pw))
|
let updatedUser = model.PopulateUser usr (fun pw -> hasher.HashPassword (usr, pw))
|
||||||
updatedUser |> if model.IsNew then ctx.Db.AddEntry else ctx.Db.UpdateEntry
|
do! Users.save updatedUser conn
|
||||||
let! _ = ctx.Db.SaveChangesAsync ()
|
let s = Views.I18N.localizer.Force ()
|
||||||
let s = Views.I18N.localizer.Force ()
|
|
||||||
if model.IsNew then
|
if model.IsNew then
|
||||||
let h = CommonFunctions.htmlString
|
let h = CommonFunctions.htmlString
|
||||||
{ UserMessage.info with
|
{ UserMessage.info with
|
||||||
|
@ -284,10 +287,11 @@ let saveGroups : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun
|
||||||
|
|
||||||
/// GET /user/[user-id]/small-groups
|
/// GET /user/[user-id]/small-groups
|
||||||
let smallGroups usrId : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> task {
|
let smallGroups usrId : HttpHandler = requireAccess [ Admin ] >=> fun next ctx -> task {
|
||||||
let userId = UserId usrId
|
let! conn = ctx.Conn
|
||||||
|
let userId = UserId usrId
|
||||||
match! ctx.Db.TryUserByIdWithGroups userId with
|
match! ctx.Db.TryUserByIdWithGroups userId with
|
||||||
| Some user ->
|
| Some user ->
|
||||||
let! groups = ctx.Db.GroupList ()
|
let! groups = SmallGroups.listAll conn
|
||||||
let curGroups = user.SmallGroups |> Seq.map (fun g -> shortGuid g.SmallGroupId.Value) |> List.ofSeq
|
let curGroups = user.SmallGroups |> Seq.map (fun g -> shortGuid g.SmallGroupId.Value) |> List.ofSeq
|
||||||
return!
|
return!
|
||||||
viewInfo ctx
|
viewInfo ctx
|
||||||
|
|
Loading…
Reference in New Issue
Block a user