Migrate 2 handlers, search form

This commit is contained in:
Daniel J. Summers 2021-07-10 11:34:51 -04:00
parent 67b169524a
commit bd8cebbbe2
4 changed files with 106 additions and 54 deletions

View File

@ -12,21 +12,20 @@ open Giraffe.EndpointRouting
let webApp = [ let webApp = [
subRoute "/api" [ subRoute "/api" [
subRoute "/citizen" [ subRoute "/citizen" [
GET [ GET_HEAD [
routef "/log-on/%s" Handlers.Citizen.logOn routef "/log-on/%s" Handlers.Citizen.logOn
routef "/get/%O" Handlers.Citizen.get routef "/get/%O" Handlers.Citizen.get
] ]
DELETE [ route "" Handlers.Citizen.delete ] DELETE [ route "" Handlers.Citizen.delete ]
] ]
subRoute "/continent" [ GET_HEAD [ route "/continent/all" Handlers.Continent.all ]
GET [ route "/all" Handlers.Continent.all ]
]
subRoute "/profile" [ subRoute "/profile" [
GET [ GET_HEAD [
route "" Handlers.Profile.current route "" Handlers.Profile.current
route "/count" Handlers.Profile.count route "/count" Handlers.Profile.count
routef "/get/%O" Handlers.Profile.get routef "/get/%O" Handlers.Profile.get
] ]
PATCH [ route "/employment-found" Handlers.Profile.employmentFound ]
POST [ route "/save" Handlers.Profile.save ] POST [ route "/save" Handlers.Profile.save ]
] ]
] ]

View File

@ -186,6 +186,49 @@ let withReconn (conn : IConnection) =
| false -> ())) | false -> ()))
/// Profile data access functions
[<RequireQualifiedAccess>]
module Profile =
let count conn =
withReconn(conn).ExecuteAsync(fun () ->
r.Table(Table.Profile)
.Count()
.RunResultAsync<int64> conn)
/// Find a profile by citizen ID
let findById (citizenId : CitizenId) conn = task {
let! profile =
withReconn(conn).ExecuteAsync(fun () ->
r.Table(Table.Profile)
.Get(citizenId)
.RunResultAsync<Profile> conn)
return toOption profile
}
/// Insert or update a profile
let save (profile : Profile) conn =
withReconn(conn).ExecuteAsync(fun () -> task {
let! _ =
r.Table(Table.Profile)
.Get(profile.id)
.Replace(profile)
.RunWriteAsync conn
()
})
/// Delete a citizen's profile
let delete (citizenId : CitizenId) conn =
withReconn(conn).ExecuteAsync(fun () -> task {
let! _ =
r.Table(Table.Profile)
.Get(citizenId)
.Delete()
.RunWriteAsync conn
()
})
/// Citizen data access functions /// Citizen data access functions
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module Citizen = module Citizen =
@ -233,13 +276,9 @@ module Citizen =
}) })
/// Delete a citizen /// Delete a citizen
let delete (citizenId : CitizenId) conn = let delete citizenId conn =
withReconn(conn).ExecuteAsync(fun () -> task { withReconn(conn).ExecuteAsync(fun () -> task {
let! _ = do! Profile.delete citizenId conn
r.Table(Table.Profile)
.Get(citizenId)
.Delete()
.RunWriteAsync conn
let! _ = let! _ =
r.Table(Table.Success) r.Table(Table.Success)
.GetAll(citizenId).OptArg("index", "citizenId") .GetAll(citizenId).OptArg("index", "citizenId")
@ -276,38 +315,6 @@ module Continent =
.RunResultAsync<Continent list> conn) .RunResultAsync<Continent list> conn)
/// Profile data access functions
[<RequireQualifiedAccess>]
module Profile =
let count conn =
withReconn(conn).ExecuteAsync(fun () ->
r.Table(Table.Profile)
.Count()
.RunResultAsync<int64> conn)
/// Find a profile by citizen ID
let findById (citizenId : CitizenId) conn = task {
let! profile =
withReconn(conn).ExecuteAsync(fun () ->
r.Table(Table.Profile)
.Get(citizenId)
.RunResultAsync<Profile> conn)
return toOption profile
}
/// Insert or update a profile
let save (profile : Profile) conn =
withReconn(conn).ExecuteAsync(fun () -> task {
let! _ =
r.Table(Table.Profile)
.Get(profile.id)
.Replace(profile)
.RunWriteAsync conn
()
})
/// Success story data access functions /// Success story data access functions
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module Success = module Success =

View File

@ -59,13 +59,12 @@ module Helpers =
/// Get the RethinkDB connection from the request context /// Get the RethinkDB connection from the request context
let conn (ctx : HttpContext) = ctx.GetService<IConnection> () let conn (ctx : HttpContext) = ctx.GetService<IConnection> ()
/// Return None if the string is null, empty, or whitespace; otherwise, return Some and the trimmed string /// `None` if a `string option` is `None`, whitespace, or empty
let noneIfEmpty x = let noneIfBlank (s : string option) =
match (defaultArg (Option.ofObj x) "").Trim () with | "" -> None | it -> Some it s |> Option.map (fun x -> match x.Trim () with "" -> None | _ -> Some x) |> Option.flatten
/// Return None if an optional string is None or empty /// `None` if a `string` is null, empty, or whitespace; otherwise, `Some` and the trimmed string
let noneIfBlank s = let noneIfEmpty = Option.ofObj >> noneIfBlank
s |> Option.map (fun x -> match x with "" -> None | _ -> Some x) |> Option.flatten
/// Try to get the current user /// Try to get the current user
let tryUser (ctx : HttpContext) = let tryUser (ctx : HttpContext) =
@ -79,7 +78,10 @@ module Helpers =
/// Get the ID of the currently logged in citizen /// Get the ID of the currently logged in citizen
// NOTE: if no one is logged in, this will raise an exception // NOTE: if no one is logged in, this will raise an exception
let currentCitizenId ctx = (tryUser >> Option.get >> CitizenId.ofString) ctx let currentCitizenId = tryUser >> Option.get >> CitizenId.ofString
/// Return an empty OK response
let ok : HttpHandler = Successful.OK ""
@ -143,7 +145,7 @@ module Citizen =
authorize authorize
>=> fun next ctx -> task { >=> fun next ctx -> task {
do! Data.Citizen.delete (currentCitizenId ctx) (conn ctx) do! Data.Citizen.delete (currentCitizenId ctx) (conn ctx)
return! Successful.OK "" next ctx return! ok next ctx
} }
@ -189,7 +191,7 @@ module Profile =
>=> fun next ctx -> task { >=> fun next ctx -> task {
let! theCount = Data.Profile.count (conn ctx) let! theCount = Data.Profile.count (conn ctx)
return! json { count = theCount } next ctx return! json { count = theCount } next ctx
} }
// POST: /api/profile/save // POST: /api/profile/save
let save : HttpHandler = let save : HttpHandler =
@ -222,6 +224,26 @@ module Profile =
}) })
} dbConn } dbConn
do! Data.Citizen.realNameUpdate citizenId (noneIfBlank (Some form.realName)) dbConn do! Data.Citizen.realNameUpdate citizenId (noneIfBlank (Some form.realName)) dbConn
return! Successful.OK "" next ctx return! ok next ctx
} }
// PATCH: /api/profile/employment-found
let employmentFound : HttpHandler =
authorize
>=> fun next ctx -> task {
let dbConn = conn ctx
match! Data.Profile.findById (currentCitizenId ctx) dbConn with
| Some profile ->
do! Data.Profile.save { profile with seekingEmployment = false } dbConn
return! ok next ctx
| None -> return! Error.notFound next ctx
}
// DELETE: /api/profile
let delete : HttpHandler =
authorize
>=> fun next ctx -> task {
do! Data.Profile.delete (currentCitizenId ctx) (conn ctx)
return! ok next ctx
}

View File

@ -78,3 +78,27 @@ module ProfileForm =
notes = s.notes notes = s.notes
}) })
} }
/// The various ways profiles can be searched
type ProfileSearch = {
/// Retrieve citizens from this continent
continentId : string option
/// Text for a search within a citizen's skills
skill : string option
/// Text for a search with a citizen's professional biography and experience fields
bioExperience : string option
/// Whether to retrieve citizens who do or do not want remote work
remoteWork : string
}
/// Support functions for profile searches
module ProfileSearch =
/// Is the search empty?
let isEmptySearch search =
[ search.continentId
search.skill
search.bioExperience
match search.remoteWork with "" -> Some search.remoteWork | _ -> None
]
|> List.exists Option.isSome