276 lines
12 KiB
Forth

module JobsJobsJobs.Profiles.Handlers
open Giraffe
open JobsJobsJobs
open JobsJobsJobs.Common.Handlers
open JobsJobsJobs.Domain
open JobsJobsJobs.Profiles.Domain
// POST: /profile/delete
let delete : HttpHandler = requireUser >=> validateCsrf >=> fun next ctx -> task {
do! Data.deleteById (currentCitizenId ctx)
do! addSuccess "Profile deleted successfully" ctx
return! redirectToGet "/citizen/dashboard" next ctx
}
// GET: /profile/edit
let edit : HttpHandler = requireUser >=> fun next ctx -> task {
let citizenId = currentCitizenId ctx
let! profile = Data.findById citizenId
let display = match profile with Some p -> p | None -> { Profile.empty with Id = citizenId }
return! Views.edit display |> render "Employment Profile" next ctx
}
// GET: /profile/edit/general
let editGeneralInfo : HttpHandler = requireUser >=> fun next ctx -> task {
let! profile = Data.findById (currentCitizenId ctx)
let! continents = Common.Data.Continents.all ()
let form = if Option.isNone profile then EditProfileForm.empty else EditProfileForm.fromProfile profile.Value
return!
Views.editGeneralInfo form continents (isHtmx ctx) (csrf ctx)
|> render "General Information | Employment Profile" next ctx
}
// POST: /profile/save
let saveGeneralInfo : HttpHandler = requireUser >=> fun next ctx -> task {
let citizenId = currentCitizenId ctx
let! form = ctx.BindFormAsync<EditProfileForm> ()
let errors = [
if form.ContinentId = "" then "Continent is required"
if form.Region = "" then "Region is required"
if form.Biography = "" then "Professional Biography is required"
]
let! profile = task {
match! Data.findById citizenId with
| Some p -> return p
| None -> return { Profile.empty with Id = citizenId }
}
let isNew = profile.Region = ""
if List.isEmpty errors then
do! Data.save
{ profile with
ContinentId = ContinentId.ofString form.ContinentId
Region = form.Region
IsSeekingEmployment = form.IsSeekingEmployment
IsRemote = form.RemoteWork
IsFullTime = form.FullTime
Biography = Text form.Biography
LastUpdatedOn = now ctx
Experience = noneIfBlank form.Experience |> Option.map Text
Visibility = ProfileVisibility.parse form.Visibility
}
let action = if isNew then "cre" else "upd"
do! addSuccess $"Employment Profile {action}ated successfully" ctx
return! redirectToGet "/profile/edit" next ctx
else
do! addErrors errors ctx
let! continents = Common.Data.Continents.all ()
return!
Views.editGeneralInfo form continents (isHtmx ctx) (csrf ctx)
|> render "General Information | Employment Profile" next ctx
}
// GET: /profile/search
let search : HttpHandler = requireUser >=> fun next ctx -> task {
let! continents = Common.Data.Continents.all ()
let form =
match ctx.TryBindQueryString<ProfileSearchForm> () with
| Ok f -> f
| Error _ -> { ContinentId = ""; RemoteWork = ""; Text = "" }
let! results = task {
if string ctx.Request.Query["searched"] = "true" then
let! it = Data.search form
return Some it
else return None
}
return! Views.search form continents (timeZone ctx) results |> render "Profile Search" next ctx
}
// GET: /profile/seeking
let seeking : HttpHandler = fun next ctx -> task {
let! continents = Common.Data.Continents.all ()
let form =
match ctx.TryBindQueryString<PublicSearchForm> () with
| Ok f -> f
| Error _ -> { ContinentId = ""; Region = ""; RemoteWork = ""; Skill = "" }
let! results = task {
if string ctx.Request.Query["searched"] = "true" then
let! it = Data.publicSearch form
return Some it
else return None
}
return! Views.publicSearch form continents results |> render "Profile Search" next ctx
}
// GET: /profile/edit/skills
let skills : HttpHandler = requireUser >=> fun next ctx -> task {
match! Data.findById (currentCitizenId ctx) with
| Some profile -> return! Views.skills profile.Skills (csrf ctx) |> render "Skills | Employment Profile" next ctx
| None -> return! notFound ctx
}
// GET: /profile/edit/skills/list
let skillList : HttpHandler = requireUser >=> fun next ctx -> task {
match! Data.findById (currentCitizenId ctx) with
| Some profile -> return! Views.skillTable profile.Skills None (csrf ctx) |> renderBare next ctx
| None -> return! notFound ctx
}
// GET: /profile/edit/skill/[idx]
let editSkill idx : HttpHandler = requireUser >=> fun next ctx -> task {
match! Data.findById (currentCitizenId ctx) with
| Some profile ->
if idx < -1 || idx >= List.length profile.Skills then return! notFound ctx
else return! Views.editSkill profile.Skills idx (csrf ctx) |> renderBare next ctx
| None -> return! notFound ctx
}
// POST: /profile/edit/skill/[idx]
let saveSkill idx : HttpHandler = requireUser >=> validateCsrf >=> fun next ctx -> task {
match! Data.findById (currentCitizenId ctx) with
| Some profile ->
if idx < -1 || idx >= List.length profile.Skills then return! notFound ctx
else
let! form = ctx.BindFormAsync<SkillForm> ()
let skill = SkillForm.toSkill form
let skills =
if idx = -1 then skill :: profile.Skills
else profile.Skills |> List.mapi (fun skillIdx it -> if skillIdx = idx then skill else it)
|> List.sortBy (fun it -> it.Description.ToLowerInvariant ())
do! Data.save { profile with Skills = skills }
return! Views.skillTable skills None (csrf ctx) |> renderBare next ctx
| None -> return! notFound ctx
}
// POST: /profile/edit/skill/[idx]/delete
let deleteSkill idx : HttpHandler = requireUser >=> validateCsrf >=> fun next ctx -> task {
match! Data.findById (currentCitizenId ctx) with
| Some profile ->
if idx < 0 || idx >= List.length profile.Skills then return! notFound ctx
else
let skills = profile.Skills |> List.indexed |> List.filter (fun it -> fst it <> idx) |> List.map snd
do! Data.save { profile with Skills = skills }
return! Views.skillTable skills None (csrf ctx) |> renderBare next ctx
| None -> return! notFound ctx
}
// GET: /profile/edit/history
let history : HttpHandler = requireUser >=> fun next ctx -> task {
match! Data.findById (currentCitizenId ctx) with
| Some profile ->
return!
Views.history profile.History (isHtmx ctx) (csrf ctx)
|> render "Employment History | Employment Profile" next ctx
| None -> return! notFound ctx
}
// GET: /profile/edit/history/list
let historyList : HttpHandler = requireUser >=> fun next ctx -> task {
match! Data.findById (currentCitizenId ctx) with
| Some profile -> return! Views.historyTable profile.History None (isHtmx ctx) (csrf ctx) |> renderBare next ctx
| None -> return! notFound ctx
}
// GET: /profile/edit/history/[idx]
let editHistory idx : HttpHandler = requireUser >=> fun next ctx -> task {
match! Data.findById (currentCitizenId ctx) with
| Some profile ->
if idx < -1 || idx >= List.length profile.History then return! notFound ctx
else return! Views.editHistory profile.History idx (isHtmx ctx) (csrf ctx) |> renderBare next ctx
| None -> return! notFound ctx
}
// POST: /profile/edit/history/[idx]
let saveHistory idx : HttpHandler = requireUser >=> validateCsrf >=> fun next ctx -> task {
match! Data.findById (currentCitizenId ctx) with
| Some profile ->
if idx < -1 || idx >= List.length profile.History then return! notFound ctx
else
let! form = ctx.BindFormAsync<HistoryForm> ()
let entry = HistoryForm.toHistory form
let history =
if idx = -1 then entry :: profile.History
else profile.History |> List.mapi (fun histIdx it -> if histIdx = idx then entry else it)
|> List.sortByDescending (fun it -> defaultArg it.EndDate NodaTime.LocalDate.MaxIsoValue)
do! Data.save { profile with History = history }
return! Views.historyTable history None (isHtmx ctx) (csrf ctx) |> renderBare next ctx
| None -> return! notFound ctx
}
// POST: /profile/edit/history/[idx]/delete
let deleteHistory idx : HttpHandler = requireUser >=> validateCsrf >=> fun next ctx -> task {
match! Data.findById (currentCitizenId ctx) with
| Some profile ->
if idx < 0 || idx >= List.length profile.History then return! notFound ctx
else
let history = profile.History |> List.indexed |> List.filter (fun it -> fst it <> idx) |> List.map snd
do! Data.save { profile with History = history }
return! Views.historyTable history None (isHtmx ctx) (csrf ctx) |> renderBare next ctx
| None -> return! notFound ctx
}
/// Get a profile for view, and enforce visibility restrictions against the current user
let private getProfileForView citizenId ctx = task {
let citId = CitizenId citizenId
match! Data.findByIdForView citId with
| Some profile ->
let currentCitizen = tryUser ctx |> Option.map CitizenId.ofString
let canView =
match profile.Profile.Visibility, currentCitizen with
| Private, Some _
| Anonymous, Some _
| Public, _ -> true
| Hidden, Some citizenId when profile.Citizen.Id = citizenId -> true
| _ -> false
return if canView then Some (profile, currentCitizen) else None
| None -> return None
}
// GET: /profile/[id]/view
let view citizenId : HttpHandler = fun next ctx -> task {
match! getProfileForView citizenId ctx with
| Some (profile, currentCitizen) ->
let title = $"Employment Profile for {Citizen.name profile.Citizen}"
return! Views.view profile currentCitizen |> render title next ctx
| None -> return! notFound ctx
}
// GET: /profile/[id]/print
let print citizenId : HttpHandler = fun next ctx -> task {
match! getProfileForView citizenId ctx with
| Some (profile, currentCitizen) ->
let pageTitle = $"Employment Profile for {Citizen.name profile.Citizen}"
return! Views.print profile (Option.isNone currentCitizen) |> renderPrint pageTitle next ctx
| None -> return! notFound ctx
}
open Giraffe.EndpointRouting
/// All endpoints for this feature
let endpoints =
subRoute "/profile" [
GET_HEAD [
routef "/%O/view" view
routef "/%O/print" print
route "/edit" edit
route "/edit/general" editGeneralInfo
routef "/edit/history/%i" editHistory
route "/edit/history" history
route "/edit/history/list" historyList
routef "/edit/skill/%i" editSkill
route "/edit/skills" skills
route "/edit/skills/list" skillList
route "/search" search
route "/seeking" seeking
]
POST [
route "/delete" delete
routef "/edit/history/%i" saveHistory
routef "/edit/history/%i/delete" deleteHistory
routef "/edit/skill/%i" saveSkill
routef "/edit/skill/%i/delete" deleteSkill
route "/save" saveGeneralInfo
]
]