Add employment history to profile type (#39)

- Add edit profile "menu" page
- Fix build errors with migration after repo reorg
This commit is contained in:
Daniel J. Summers 2023-01-19 23:04:41 -05:00
parent 78e21f2429
commit 93da2831cb
5 changed files with 126 additions and 40 deletions

View File

@ -44,6 +44,55 @@ module ContinentId =
let value = function ContinentId guid -> guid
/// A string of Markdown text
type MarkdownString = Text of string
/// Support functions for Markdown strings
module MarkdownString =
open Markdig
/// The Markdown conversion pipeline (enables all advanced features)
let private pipeline = MarkdownPipelineBuilder().UseAdvancedExtensions().Build ()
/// Convert this Markdown string to HTML
let toHtml = function Text text -> Markdown.ToHtml (text, pipeline)
/// Convert a Markdown string to its string representation
let toString = function Text text -> text
/// An employment history entry
[<NoComparison; NoEquality>]
type EmploymentHistory =
{ /// The employer for this period of employment
Employer : string
/// The date employment started
StartDate : LocalDate
/// The date employment ended (None implies ongoing employment)
EndDate : LocalDate option
/// The title / position held
Position : string option
/// A description of the duties entailed during this employment
Description : MarkdownString option
}
/// Support functions for employment history entries
module EmploymentHistory =
let empty =
{ Employer = ""
StartDate = LocalDate.MinIsoValue
EndDate = None
Position = None
Description = None
}
/// The ID of a job listing
type ListingId = ListingId of Guid
@ -63,24 +112,6 @@ module ListingId =
let value = function ListingId guid -> guid
/// A string of Markdown text
type MarkdownString = Text of string
/// Support functions for Markdown strings
module MarkdownString =
open Markdig
/// The Markdown conversion pipeline (enables all advanced features)
let private pipeline = MarkdownPipelineBuilder().UseAdvancedExtensions().Build ()
/// Convert this Markdown string to HTML
let toHtml = function Text text -> Markdown.ToHtml (text, pipeline)
/// Convert a Markdown string to its string representation
let toString = function Text text -> text
/// Types of contacts supported by Jobs, Jobs, Jobs
type ContactType =
/// E-mail addresses
@ -363,14 +394,17 @@ type Profile =
/// The citizen's professional biography
Biography : MarkdownString
/// When the citizen last updated their profile
LastUpdatedOn : Instant
/// Skills this citizen possesses
Skills : Skill list
/// The citizen's employment history
History : EmploymentHistory list
/// The citizen's experience (topical / chronological)
Experience : MarkdownString option
/// Skills this citizen possesses
Skills : Skill list
/// When the citizen last updated their profile
LastUpdatedOn : Instant
/// Whether this is a legacy profile
IsLegacy : bool
@ -390,9 +424,10 @@ module Profile =
IsRemote = false
IsFullTime = false
Biography = Text ""
LastUpdatedOn = Instant.MinValue
Experience = None
Skills = []
History = []
Experience = None
LastUpdatedOn = Instant.MinValue
IsLegacy = false
}

View File

@ -15,7 +15,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Data\JobsJobsJobs.Data.fsproj" />
<ProjectReference Include="..\Application\JobsJobsJobs.Application.fsproj" />
</ItemGroup>
</Project>

View File

@ -42,7 +42,8 @@ module Rethink =
/// Shorthand for the RethinkDB R variable (how every command starts)
let r = RethinkDb.Driver.RethinkDB.R
open JobsJobsJobs.Data
open JobsJobsJobs
open JobsJobsJobs.Common.Data
open JobsJobsJobs.Domain
open Newtonsoft.Json.Linq
open NodaTime.Text
@ -63,8 +64,8 @@ task {
// Establish database connections
let cfg = ConfigurationBuilder().AddJsonFile("appsettings.json").Build ()
use rethinkConn = Rethink.Startup.createConnection (cfg.GetConnectionString "RethinkDB")
do! DataConnection.setUp cfg
let pgConn = DataConnection.dataSource ()
do! setUp cfg
let pgConn = dataSource ()
let getOld table =
fromTable table
@ -88,7 +89,7 @@ task {
IsLegacy = true
})
for citizen in newCitizens do
do! Citizens.save citizen
do! Citizens.Data.save citizen
let! _ =
pgConn
|> Sql.executeTransactionAsync [
@ -148,7 +149,7 @@ task {
IsLegacy = true
})
for profile in newProfiles do
do! Profiles.save profile
do! Profiles.Data.save profile
printfn $"** Migrated {List.length newProfiles} profiles"
// Migrate listings
@ -179,7 +180,7 @@ task {
IsLegacy = true
})
for listing in newListings do
do! Listings.save listing
do! Listings.Data.save listing
printfn $"** Migrated {List.length newListings} listings"
// Migrate success stories
@ -196,7 +197,7 @@ task {
Story = if isNull story then None else Some (Text story)
})
for success in newSuccesses do
do! Successes.save success
do! SuccessStories.Data.save success
printfn $"** Migrated {List.length newSuccesses} successes"
// Delete any citizens who have no profile, no listing, and no success story recorded

View File

@ -15,13 +15,21 @@ let delete : HttpHandler = requireUser >=> validateCsrf >=> fun next ctx -> task
// 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 citizenId = currentCitizenId ctx
let! profile = Data.findById citizenId
let! continents = Common.Data.Continents.all ()
let isNew = Option.isNone profile
let form = if isNew then EditProfileForm.empty else EditProfileForm.fromProfile profile.Value
let title = $"""{if isNew then "Create" else "Edit"} Profile"""
return! Views.edit form continents isNew citizenId (csrf ctx) |> render title next ctx
return! Views.editGeneralInfo form continents isNew citizenId (csrf ctx) |> render title next ctx
}
// POST: /profile/save
@ -66,7 +74,7 @@ let save : HttpHandler = requireUser >=> fun next ctx -> task {
do! addErrors errors ctx
let! continents = Common.Data.Continents.all ()
return!
Views.edit form continents isNew citizenId (csrf ctx)
Views.editGeneralInfo form continents isNew citizenId (csrf ctx)
|> render $"""{if isNew then "Create" else "Edit"} Profile""" next ctx
}
@ -125,6 +133,7 @@ let endpoints =
GET_HEAD [
routef "/%O/view" view
route "/edit" edit
route "/edit/general" editGeneralInfo
route "/search" search
route "/seeking" seeking
]

View File

@ -7,6 +7,47 @@ open JobsJobsJobs.Common.Views
open JobsJobsJobs.Domain
open JobsJobsJobs.Profiles.Domain
/// The profile edit menu page
let edit (profile : Profile) =
let hasProfile = profile.Region <> ""
pageWithTitle "Employment Profile" [
p [] [ txt "There are three different sections to the employment profile." ]
ul [] [
li [ _class "mb-2" ] [
a [ _href $"/profile/edit/general" ] [ strong [] [ txt "General Information" ] ]; br []
txt "contains your location, professional biography, and information about the type of employment you "
txt "may be seeking."
if not hasProfile then txt " Entering information here will create your profile."
]
if hasProfile then
li [ _class "mb-2" ] [
let skillCount = List.length profile.Skills
a [ _href $"/profile/edit/skills" ] [ strong [] [ txt "Skills" ] ]; br []
txt "is where you can list skills you have acquired through education or experience."
em [] [
txt $" (Your profile currently lists {skillCount} skill"; if skillCount <> 1 then txt "s"
txt ".)"
]
]
li [ _class "mb-2" ] [
let historyCount = List.length profile.History
a [ _href $"/profile/edit/history" ] [ strong [] [ txt "Employment History" ] ]; br []
txt "is where you can record a chronological history of your employment."
em [] [
txt $" (Your profile contains {historyCount} employment history entr"
txt (if historyCount <> 1 then "ies" else "y"); txt ".)"
]
]
]
if hasProfile then
p [] [
a [ _class "btn btn-primary"; _href $"/profile/{CitizenId.toString profile.Id}/view" ] [
i [ _class "mdi mdi-file-account-outline" ] []; txt "&nbsp; View Your User Profile"
]
]
]
/// Render the skill edit template and existing skills
let skillEdit (skills : SkillForm array) =
let mapToInputs (idx : int) (skill : SkillForm) =
@ -38,7 +79,7 @@ let skillEdit (skills : SkillForm array) =
:: (skills |> Array.mapi mapToInputs |> List.ofArray)
/// The profile edit page
let edit (m : EditProfileForm) continents isNew citizenId csrf =
let editGeneralInfo (m : EditProfileForm) continents isNew citizenId csrf =
pageWithTitle "My Employment Profile" [
form [ _class "row g-3"; _action "/profile/save"; _hxPost "/profile/save" ] [
antiForgery csrf