Version 3 #40
|
@ -44,6 +44,55 @@ module ContinentId =
|
||||||
let value = function ContinentId guid -> guid
|
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
|
/// The ID of a job listing
|
||||||
type ListingId = ListingId of Guid
|
type ListingId = ListingId of Guid
|
||||||
|
|
||||||
|
@ -63,24 +112,6 @@ module ListingId =
|
||||||
let value = function ListingId guid -> guid
|
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
|
/// Types of contacts supported by Jobs, Jobs, Jobs
|
||||||
type ContactType =
|
type ContactType =
|
||||||
/// E-mail addresses
|
/// E-mail addresses
|
||||||
|
@ -363,14 +394,17 @@ type Profile =
|
||||||
/// The citizen's professional biography
|
/// The citizen's professional biography
|
||||||
Biography : MarkdownString
|
Biography : MarkdownString
|
||||||
|
|
||||||
/// When the citizen last updated their profile
|
/// Skills this citizen possesses
|
||||||
LastUpdatedOn : Instant
|
Skills : Skill list
|
||||||
|
|
||||||
|
/// The citizen's employment history
|
||||||
|
History : EmploymentHistory list
|
||||||
|
|
||||||
/// The citizen's experience (topical / chronological)
|
/// The citizen's experience (topical / chronological)
|
||||||
Experience : MarkdownString option
|
Experience : MarkdownString option
|
||||||
|
|
||||||
/// Skills this citizen possesses
|
/// When the citizen last updated their profile
|
||||||
Skills : Skill list
|
LastUpdatedOn : Instant
|
||||||
|
|
||||||
/// Whether this is a legacy profile
|
/// Whether this is a legacy profile
|
||||||
IsLegacy : bool
|
IsLegacy : bool
|
||||||
|
@ -390,9 +424,10 @@ module Profile =
|
||||||
IsRemote = false
|
IsRemote = false
|
||||||
IsFullTime = false
|
IsFullTime = false
|
||||||
Biography = Text ""
|
Biography = Text ""
|
||||||
LastUpdatedOn = Instant.MinValue
|
|
||||||
Experience = None
|
|
||||||
Skills = []
|
Skills = []
|
||||||
|
History = []
|
||||||
|
Experience = None
|
||||||
|
LastUpdatedOn = Instant.MinValue
|
||||||
IsLegacy = false
|
IsLegacy = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Data\JobsJobsJobs.Data.fsproj" />
|
<ProjectReference Include="..\Application\JobsJobsJobs.Application.fsproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -42,7 +42,8 @@ module Rethink =
|
||||||
/// Shorthand for the RethinkDB R variable (how every command starts)
|
/// Shorthand for the RethinkDB R variable (how every command starts)
|
||||||
let r = RethinkDb.Driver.RethinkDB.R
|
let r = RethinkDb.Driver.RethinkDB.R
|
||||||
|
|
||||||
open JobsJobsJobs.Data
|
open JobsJobsJobs
|
||||||
|
open JobsJobsJobs.Common.Data
|
||||||
open JobsJobsJobs.Domain
|
open JobsJobsJobs.Domain
|
||||||
open Newtonsoft.Json.Linq
|
open Newtonsoft.Json.Linq
|
||||||
open NodaTime.Text
|
open NodaTime.Text
|
||||||
|
@ -63,8 +64,8 @@ task {
|
||||||
// Establish database connections
|
// Establish database connections
|
||||||
let cfg = ConfigurationBuilder().AddJsonFile("appsettings.json").Build ()
|
let cfg = ConfigurationBuilder().AddJsonFile("appsettings.json").Build ()
|
||||||
use rethinkConn = Rethink.Startup.createConnection (cfg.GetConnectionString "RethinkDB")
|
use rethinkConn = Rethink.Startup.createConnection (cfg.GetConnectionString "RethinkDB")
|
||||||
do! DataConnection.setUp cfg
|
do! setUp cfg
|
||||||
let pgConn = DataConnection.dataSource ()
|
let pgConn = dataSource ()
|
||||||
|
|
||||||
let getOld table =
|
let getOld table =
|
||||||
fromTable table
|
fromTable table
|
||||||
|
@ -88,7 +89,7 @@ task {
|
||||||
IsLegacy = true
|
IsLegacy = true
|
||||||
})
|
})
|
||||||
for citizen in newCitizens do
|
for citizen in newCitizens do
|
||||||
do! Citizens.save citizen
|
do! Citizens.Data.save citizen
|
||||||
let! _ =
|
let! _ =
|
||||||
pgConn
|
pgConn
|
||||||
|> Sql.executeTransactionAsync [
|
|> Sql.executeTransactionAsync [
|
||||||
|
@ -148,7 +149,7 @@ task {
|
||||||
IsLegacy = true
|
IsLegacy = true
|
||||||
})
|
})
|
||||||
for profile in newProfiles do
|
for profile in newProfiles do
|
||||||
do! Profiles.save profile
|
do! Profiles.Data.save profile
|
||||||
printfn $"** Migrated {List.length newProfiles} profiles"
|
printfn $"** Migrated {List.length newProfiles} profiles"
|
||||||
|
|
||||||
// Migrate listings
|
// Migrate listings
|
||||||
|
@ -179,7 +180,7 @@ task {
|
||||||
IsLegacy = true
|
IsLegacy = true
|
||||||
})
|
})
|
||||||
for listing in newListings do
|
for listing in newListings do
|
||||||
do! Listings.save listing
|
do! Listings.Data.save listing
|
||||||
printfn $"** Migrated {List.length newListings} listings"
|
printfn $"** Migrated {List.length newListings} listings"
|
||||||
|
|
||||||
// Migrate success stories
|
// Migrate success stories
|
||||||
|
@ -196,7 +197,7 @@ task {
|
||||||
Story = if isNull story then None else Some (Text story)
|
Story = if isNull story then None else Some (Text story)
|
||||||
})
|
})
|
||||||
for success in newSuccesses do
|
for success in newSuccesses do
|
||||||
do! Successes.save success
|
do! SuccessStories.Data.save success
|
||||||
printfn $"** Migrated {List.length newSuccesses} successes"
|
printfn $"** Migrated {List.length newSuccesses} successes"
|
||||||
|
|
||||||
// Delete any citizens who have no profile, no listing, and no success story recorded
|
// Delete any citizens who have no profile, no listing, and no success story recorded
|
||||||
|
|
|
@ -15,13 +15,21 @@ let delete : HttpHandler = requireUser >=> validateCsrf >=> fun next ctx -> task
|
||||||
|
|
||||||
// GET: /profile/edit
|
// GET: /profile/edit
|
||||||
let edit : HttpHandler = requireUser >=> fun next ctx -> task {
|
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 citizenId = currentCitizenId ctx
|
||||||
let! profile = Data.findById citizenId
|
let! profile = Data.findById citizenId
|
||||||
let! continents = Common.Data.Continents.all ()
|
let! continents = Common.Data.Continents.all ()
|
||||||
let isNew = Option.isNone profile
|
let isNew = Option.isNone profile
|
||||||
let form = if isNew then EditProfileForm.empty else EditProfileForm.fromProfile profile.Value
|
let form = if isNew then EditProfileForm.empty else EditProfileForm.fromProfile profile.Value
|
||||||
let title = $"""{if isNew then "Create" else "Edit"} Profile"""
|
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
|
// POST: /profile/save
|
||||||
|
@ -66,7 +74,7 @@ let save : HttpHandler = requireUser >=> fun next ctx -> task {
|
||||||
do! addErrors errors ctx
|
do! addErrors errors ctx
|
||||||
let! continents = Common.Data.Continents.all ()
|
let! continents = Common.Data.Continents.all ()
|
||||||
return!
|
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
|
|> render $"""{if isNew then "Create" else "Edit"} Profile""" next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,6 +133,7 @@ let endpoints =
|
||||||
GET_HEAD [
|
GET_HEAD [
|
||||||
routef "/%O/view" view
|
routef "/%O/view" view
|
||||||
route "/edit" edit
|
route "/edit" edit
|
||||||
|
route "/edit/general" editGeneralInfo
|
||||||
route "/search" search
|
route "/search" search
|
||||||
route "/seeking" seeking
|
route "/seeking" seeking
|
||||||
]
|
]
|
||||||
|
|
|
@ -7,6 +7,47 @@ open JobsJobsJobs.Common.Views
|
||||||
open JobsJobsJobs.Domain
|
open JobsJobsJobs.Domain
|
||||||
open JobsJobsJobs.Profiles.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 " View Your User Profile"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
/// Render the skill edit template and existing skills
|
/// Render the skill edit template and existing skills
|
||||||
let skillEdit (skills : SkillForm array) =
|
let skillEdit (skills : SkillForm array) =
|
||||||
let mapToInputs (idx : int) (skill : SkillForm) =
|
let mapToInputs (idx : int) (skill : SkillForm) =
|
||||||
|
@ -38,7 +79,7 @@ let skillEdit (skills : SkillForm array) =
|
||||||
:: (skills |> Array.mapi mapToInputs |> List.ofArray)
|
:: (skills |> Array.mapi mapToInputs |> List.ofArray)
|
||||||
|
|
||||||
/// The profile edit page
|
/// The profile edit page
|
||||||
let edit (m : EditProfileForm) continents isNew citizenId csrf =
|
let editGeneralInfo (m : EditProfileForm) continents isNew citizenId csrf =
|
||||||
pageWithTitle "My Employment Profile" [
|
pageWithTitle "My Employment Profile" [
|
||||||
form [ _class "row g-3"; _action "/profile/save"; _hxPost "/profile/save" ] [
|
form [ _class "row g-3"; _action "/profile/save"; _hxPost "/profile/save" ] [
|
||||||
antiForgery csrf
|
antiForgery csrf
|
||||||
|
|
Loading…
Reference in New Issue
Block a user