Version 3 #40
|
@ -410,7 +410,7 @@
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<p class="fst-italic">
|
<p class="fst-italic">
|
||||||
Changes on August 30<sup>th</sup>, 2022 –
|
Changes on August 30<sup>th</sup>, 2022
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li class="fst-italic">Removed references to Mastodon</li>
|
<li class="fst-italic">Removed references to Mastodon</li>
|
||||||
|
|
|
@ -335,7 +335,7 @@ module Listings =
|
||||||
|
|
||||||
/// Map a result for a listing view
|
/// Map a result for a listing view
|
||||||
let private toListingForView row =
|
let private toListingForView row =
|
||||||
{ listing = toDocument<Listing> row; continent = toDocumentFrom<Continent> "cont_data" row }
|
{ Listing = toDocument<Listing> row; Continent = toDocumentFrom<Continent> "cont_data" row }
|
||||||
|
|
||||||
/// Find all job listings posted by the given citizen
|
/// Find all job listings posted by the given citizen
|
||||||
let findByCitizen citizenId =
|
let findByCitizen citizenId =
|
||||||
|
@ -369,15 +369,15 @@ module Listings =
|
||||||
/// Search job listings
|
/// Search job listings
|
||||||
let search (search : ListingSearch) =
|
let search (search : ListingSearch) =
|
||||||
let searches = [
|
let searches = [
|
||||||
match search.continentId with
|
match search.ContinentId with
|
||||||
| Some contId -> "l.data ->> 'continentId' = @continentId", [ "@continentId", Sql.string contId ]
|
| Some contId -> "l.data ->> 'continentId' = @continentId", [ "@continentId", Sql.string contId ]
|
||||||
| None -> ()
|
| None -> ()
|
||||||
match search.region with
|
match search.Region with
|
||||||
| Some region -> "l.data ->> 'region' ILIKE @region", [ "@region", like region ]
|
| Some region -> "l.data ->> 'region' ILIKE @region", [ "@region", like region ]
|
||||||
| None -> ()
|
| None -> ()
|
||||||
if search.remoteWork <> "" then
|
if search.RemoteWork <> "" then
|
||||||
"l.data ->> 'isRemote' = @remote", [ "@remote", jsonBool (search.remoteWork = "yes") ]
|
"l.data ->> 'isRemote' = @remote", [ "@remote", jsonBool (search.RemoteWork = "yes") ]
|
||||||
match search.text with
|
match search.Text with
|
||||||
| Some text -> "l.data ->> 'text' ILIKE @text", [ "@text", like text ]
|
| Some text -> "l.data ->> 'text' ILIKE @text", [ "@text", like text ]
|
||||||
| None -> ()
|
| None -> ()
|
||||||
]
|
]
|
||||||
|
@ -431,9 +431,9 @@ module Profiles =
|
||||||
AND p.data ->> 'isLegacy' = 'false'"
|
AND p.data ->> 'isLegacy' = 'false'"
|
||||||
|> Sql.parameters [ "@id", Sql.string (CitizenId.toString citizenId) ]
|
|> Sql.parameters [ "@id", Sql.string (CitizenId.toString citizenId) ]
|
||||||
|> Sql.executeAsync (fun row ->
|
|> Sql.executeAsync (fun row ->
|
||||||
{ profile = toDocument<Profile> row
|
{ Profile = toDocument<Profile> row
|
||||||
citizen = toDocumentFrom<Citizen> "cit_data" row
|
Citizen = toDocumentFrom<Citizen> "cit_data" row
|
||||||
continent = toDocumentFrom<Continent> "cont_data" row
|
Continent = toDocumentFrom<Continent> "cont_data" row
|
||||||
})
|
})
|
||||||
return List.tryHead tryCitizen
|
return List.tryHead tryCitizen
|
||||||
}
|
}
|
||||||
|
@ -445,15 +445,15 @@ module Profiles =
|
||||||
/// Search profiles (logged-on users)
|
/// Search profiles (logged-on users)
|
||||||
let search (search : ProfileSearch) = backgroundTask {
|
let search (search : ProfileSearch) = backgroundTask {
|
||||||
let searches = [
|
let searches = [
|
||||||
match search.continentId with
|
match search.ContinentId with
|
||||||
| Some contId -> "p.data ->> 'continentId' = @continentId", [ "@continentId", Sql.string contId ]
|
| Some contId -> "p.data ->> 'continentId' = @continentId", [ "@continentId", Sql.string contId ]
|
||||||
| None -> ()
|
| None -> ()
|
||||||
if search.remoteWork <> "" then
|
if search.RemoteWork <> "" then
|
||||||
"p.data ->> 'remoteWork' = @remote", [ "@remote", jsonBool (search.remoteWork = "yes") ]
|
"p.data ->> 'remoteWork' = @remote", [ "@remote", jsonBool (search.RemoteWork = "yes") ]
|
||||||
match search.skill with
|
match search.Skill with
|
||||||
| Some skl -> "p.data -> 'skills' ->> 'description' ILIKE @description", [ "@description", like skl ]
|
| Some skl -> "p.data -> 'skills' ->> 'description' ILIKE @description", [ "@description", like skl ]
|
||||||
| None -> ()
|
| None -> ()
|
||||||
match search.bioExperience with
|
match search.BioExperience with
|
||||||
| Some text ->
|
| Some text ->
|
||||||
"(p.data ->> 'biography' ILIKE @text OR p.data ->> 'experience' ILIKE @text)",
|
"(p.data ->> 'biography' ILIKE @text OR p.data ->> 'experience' ILIKE @text)",
|
||||||
[ "@text", Sql.string text ]
|
[ "@text", Sql.string text ]
|
||||||
|
@ -471,28 +471,28 @@ module Profiles =
|
||||||
|> Sql.executeAsync (fun row ->
|
|> Sql.executeAsync (fun row ->
|
||||||
let profile = toDocument<Profile> row
|
let profile = toDocument<Profile> row
|
||||||
let citizen = toDocumentFrom<Citizen> "cit_data" row
|
let citizen = toDocumentFrom<Citizen> "cit_data" row
|
||||||
{ citizenId = profile.Id
|
{ CitizenId = profile.Id
|
||||||
displayName = Citizen.name citizen
|
DisplayName = Citizen.name citizen
|
||||||
seekingEmployment = profile.IsSeekingEmployment
|
SeekingEmployment = profile.IsSeekingEmployment
|
||||||
remoteWork = profile.IsRemote
|
RemoteWork = profile.IsRemote
|
||||||
fullTime = profile.IsFullTime
|
FullTime = profile.IsFullTime
|
||||||
lastUpdatedOn = profile.LastUpdatedOn
|
LastUpdatedOn = profile.LastUpdatedOn
|
||||||
})
|
})
|
||||||
return results |> List.sortBy (fun psr -> psr.displayName.ToLowerInvariant ())
|
return results |> List.sortBy (fun psr -> psr.DisplayName.ToLowerInvariant ())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search profiles (public)
|
// Search profiles (public)
|
||||||
let publicSearch (search : PublicSearch) =
|
let publicSearch (search : PublicSearch) =
|
||||||
let searches = [
|
let searches = [
|
||||||
match search.continentId with
|
match search.ContinentId with
|
||||||
| Some contId -> "p.data ->> 'continentId' = @continentId", [ "@continentId", Sql.string contId ]
|
| Some contId -> "p.data ->> 'continentId' = @continentId", [ "@continentId", Sql.string contId ]
|
||||||
| None -> ()
|
| None -> ()
|
||||||
match search.region with
|
match search.Region with
|
||||||
| Some region -> "p.data ->> 'region' ILIKE @region", [ "@region", like region ]
|
| Some region -> "p.data ->> 'region' ILIKE @region", [ "@region", like region ]
|
||||||
| None -> ()
|
| None -> ()
|
||||||
if search.remoteWork <> "" then
|
if search.RemoteWork <> "" then
|
||||||
"p.data ->> 'remoteWork' = @remote", [ "@remote", jsonBool (search.remoteWork = "yes") ]
|
"p.data ->> 'remoteWork' = @remote", [ "@remote", jsonBool (search.RemoteWork = "yes") ]
|
||||||
match search.skill with
|
match search.Skill with
|
||||||
| Some skl ->
|
| Some skl ->
|
||||||
"p.data -> 'skills' ->> 'description' ILIKE @description", [ "@description", like skl ]
|
"p.data -> 'skills' ->> 'description' ILIKE @description", [ "@description", like skl ]
|
||||||
| None -> ()
|
| None -> ()
|
||||||
|
@ -508,10 +508,10 @@ module Profiles =
|
||||||
|> Sql.executeAsync (fun row ->
|
|> Sql.executeAsync (fun row ->
|
||||||
let profile = toDocument<Profile> row
|
let profile = toDocument<Profile> row
|
||||||
let continent = toDocumentFrom<Continent> "cont_data" row
|
let continent = toDocumentFrom<Continent> "cont_data" row
|
||||||
{ continent = continent.Name
|
{ Continent = continent.Name
|
||||||
region = profile.Region
|
Region = profile.Region
|
||||||
remoteWork = profile.IsRemote
|
RemoteWork = profile.IsRemote
|
||||||
skills = profile.Skills
|
Skills = profile.Skills
|
||||||
|> List.map (fun s ->
|
|> List.map (fun s ->
|
||||||
let notes = match s.Notes with Some n -> $" ({n})" | None -> ""
|
let notes = match s.Notes with Some n -> $" ({n})" | None -> ""
|
||||||
$"{s.Description}{notes}")
|
$"{s.Description}{notes}")
|
||||||
|
@ -532,12 +532,12 @@ module Successes =
|
||||||
|> Sql.executeAsync (fun row ->
|
|> Sql.executeAsync (fun row ->
|
||||||
let success = toDocument<Success> row
|
let success = toDocument<Success> row
|
||||||
let citizen = toDocumentFrom<Citizen> "cit_data" row
|
let citizen = toDocumentFrom<Citizen> "cit_data" row
|
||||||
{ id = success.Id
|
{ Id = success.Id
|
||||||
citizenId = success.CitizenId
|
CitizenId = success.CitizenId
|
||||||
citizenName = Citizen.name citizen
|
CitizenName = Citizen.name citizen
|
||||||
recordedOn = success.RecordedOn
|
RecordedOn = success.RecordedOn
|
||||||
fromHere = success.IsFromHere
|
FromHere = success.IsFromHere
|
||||||
hasStory = Option.isSome success.Story
|
HasStory = Option.isSome success.Story
|
||||||
})
|
})
|
||||||
|
|
||||||
/// Find a success story by its ID
|
/// Find a success story by its ID
|
||||||
|
|
|
@ -5,8 +5,6 @@ open JobsJobsJobs.Domain
|
||||||
open Microsoft.Extensions.Options
|
open Microsoft.Extensions.Options
|
||||||
open NodaTime
|
open NodaTime
|
||||||
|
|
||||||
// fsharplint:disable FieldNames
|
|
||||||
|
|
||||||
/// The data required to register a new citizen (user)
|
/// The data required to register a new citizen (user)
|
||||||
type CitizenRegistrationForm =
|
type CitizenRegistrationForm =
|
||||||
{ /// The first name of the new citizen
|
{ /// The first name of the new citizen
|
||||||
|
@ -31,45 +29,45 @@ type CitizenRegistrationForm =
|
||||||
/// The data required to add or edit a job listing
|
/// The data required to add or edit a job listing
|
||||||
type ListingForm =
|
type ListingForm =
|
||||||
{ /// The ID of the listing
|
{ /// The ID of the listing
|
||||||
id : string
|
Id : string
|
||||||
|
|
||||||
/// The listing title
|
/// The listing title
|
||||||
title : string
|
Title : string
|
||||||
|
|
||||||
/// The ID of the continent on which this opportunity exists
|
/// The ID of the continent on which this opportunity exists
|
||||||
continentId : string
|
ContinentId : string
|
||||||
|
|
||||||
/// The region in which this opportunity exists
|
/// The region in which this opportunity exists
|
||||||
region : string
|
Region : string
|
||||||
|
|
||||||
/// Whether this is a remote work opportunity
|
/// Whether this is a remote work opportunity
|
||||||
remoteWork : bool
|
RemoteWork : bool
|
||||||
|
|
||||||
/// The text of the job listing
|
/// The text of the job listing
|
||||||
text : string
|
Text : string
|
||||||
|
|
||||||
/// The date by which this job listing is needed
|
/// The date by which this job listing is needed
|
||||||
neededBy : string option
|
NeededBy : string option
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// The data needed to display a listing
|
/// The data needed to display a listing
|
||||||
type ListingForView =
|
type ListingForView =
|
||||||
{ /// The listing itself
|
{ /// The listing itself
|
||||||
listing : Listing
|
Listing : Listing
|
||||||
|
|
||||||
/// The continent for that listing
|
/// The continent for that listing
|
||||||
continent : Continent
|
Continent : Continent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// The form submitted to expire a listing
|
/// The form submitted to expire a listing
|
||||||
type ListingExpireForm =
|
type ListingExpireForm =
|
||||||
{ /// Whether the job was filled from here
|
{ /// Whether the job was filled from here
|
||||||
fromHere : bool
|
FromHere : bool
|
||||||
|
|
||||||
/// The success story written by the user
|
/// The success story written by the user
|
||||||
successStory : string option
|
SuccessStory : string option
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -77,46 +75,39 @@ type ListingExpireForm =
|
||||||
[<CLIMutable>]
|
[<CLIMutable>]
|
||||||
type ListingSearch =
|
type ListingSearch =
|
||||||
{ /// Retrieve job listings for this continent
|
{ /// Retrieve job listings for this continent
|
||||||
continentId : string option
|
ContinentId : string option
|
||||||
|
|
||||||
/// Text for a search within a region
|
/// Text for a search within a region
|
||||||
region : string option
|
Region : string option
|
||||||
|
|
||||||
/// Whether to retrieve job listings for remote work
|
/// Whether to retrieve job listings for remote work
|
||||||
remoteWork : string
|
RemoteWork : string
|
||||||
|
|
||||||
/// Text for a search with the job listing description
|
/// Text for a search with the job listing description
|
||||||
text : string option
|
Text : string option
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// The fields needed to log on to Jobs, Jobs, Jobs
|
/// The fields needed to log on to Jobs, Jobs, Jobs
|
||||||
type LogOnForm =
|
type LogOnForm =
|
||||||
{ /// The e-mail address for the citizen
|
{ /// The e-mail address for the citizen
|
||||||
email : string
|
Email : string
|
||||||
|
|
||||||
/// The password provided by the user
|
/// The password provided by the user
|
||||||
password : string
|
Password : string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// A successful logon
|
/// A successful logon
|
||||||
type LogOnSuccess =
|
type LogOnSuccess =
|
||||||
{ /// The JSON Web Token (JWT) to use for API access
|
{ /// The JSON Web Token (JWT) to use for API access
|
||||||
jwt : string
|
Jwt : string
|
||||||
|
|
||||||
/// The ID of the logged-in citizen (as a string)
|
/// The ID of the logged-in citizen (as a string)
|
||||||
citizenId : string
|
CitizenId : string
|
||||||
|
|
||||||
/// The name of the logged-in citizen
|
/// The name of the logged-in citizen
|
||||||
name : string
|
Name : string
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// A count
|
|
||||||
type Count =
|
|
||||||
{ // The count being returned
|
|
||||||
count : int64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -133,44 +124,44 @@ type AuthOptions () =
|
||||||
/// The fields required for a skill
|
/// The fields required for a skill
|
||||||
type SkillForm =
|
type SkillForm =
|
||||||
{ /// The ID of this skill
|
{ /// The ID of this skill
|
||||||
id : string
|
Id : string
|
||||||
|
|
||||||
/// The description of the skill
|
/// The description of the skill
|
||||||
description : string
|
Description : string
|
||||||
|
|
||||||
/// Notes regarding the skill
|
/// Notes regarding the skill
|
||||||
notes : string option
|
Notes : string option
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The data required to update a profile
|
/// The data required to update a profile
|
||||||
[<CLIMutable; NoComparison; NoEquality>]
|
[<CLIMutable; NoComparison; NoEquality>]
|
||||||
type ProfileForm =
|
type ProfileForm =
|
||||||
{ /// Whether the citizen to whom this profile belongs is actively seeking employment
|
{ /// Whether the citizen to whom this profile belongs is actively seeking employment
|
||||||
isSeekingEmployment : bool
|
IsSeekingEmployment : bool
|
||||||
|
|
||||||
/// Whether this profile should appear in the public search
|
/// Whether this profile should appear in the public search
|
||||||
isPublic : bool
|
IsPublic : bool
|
||||||
|
|
||||||
/// The ID of the continent on which the citizen is located
|
/// The ID of the continent on which the citizen is located
|
||||||
continentId : string
|
ContinentId : string
|
||||||
|
|
||||||
/// The area within that continent where the citizen is located
|
/// The area within that continent where the citizen is located
|
||||||
region : string
|
Region : string
|
||||||
|
|
||||||
/// If the citizen is available for remote work
|
/// If the citizen is available for remote work
|
||||||
remoteWork : bool
|
RemoteWork : bool
|
||||||
|
|
||||||
/// If the citizen is seeking full-time employment
|
/// If the citizen is seeking full-time employment
|
||||||
fullTime : bool
|
FullTime : bool
|
||||||
|
|
||||||
/// The user's professional biography
|
/// The user's professional biography
|
||||||
biography : string
|
Biography : string
|
||||||
|
|
||||||
/// The user's past experience
|
/// The user's past experience
|
||||||
experience : string option
|
Experience : string option
|
||||||
|
|
||||||
/// The skills for the user
|
/// The skills for the user
|
||||||
skills : SkillForm list
|
Skills : SkillForm list
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Support functions for the ProfileForm type
|
/// Support functions for the ProfileForm type
|
||||||
|
@ -178,19 +169,19 @@ module ProfileForm =
|
||||||
|
|
||||||
/// Create an instance of this form from the given profile
|
/// Create an instance of this form from the given profile
|
||||||
let fromProfile (profile : Profile) =
|
let fromProfile (profile : Profile) =
|
||||||
{ isSeekingEmployment = profile.IsSeekingEmployment
|
{ IsSeekingEmployment = profile.IsSeekingEmployment
|
||||||
isPublic = profile.IsPubliclySearchable
|
IsPublic = profile.IsPubliclySearchable
|
||||||
continentId = string profile.ContinentId
|
ContinentId = string profile.ContinentId
|
||||||
region = profile.Region
|
Region = profile.Region
|
||||||
remoteWork = profile.IsRemote
|
RemoteWork = profile.IsRemote
|
||||||
fullTime = profile.IsFullTime
|
FullTime = profile.IsFullTime
|
||||||
biography = MarkdownString.toString profile.Biography
|
Biography = MarkdownString.toString profile.Biography
|
||||||
experience = profile.Experience |> Option.map MarkdownString.toString
|
Experience = profile.Experience |> Option.map MarkdownString.toString
|
||||||
skills = profile.Skills
|
Skills = profile.Skills
|
||||||
|> List.map (fun s ->
|
|> List.map (fun s ->
|
||||||
{ id = string s.Id
|
{ Id = string s.Id
|
||||||
description = s.Description
|
Description = s.Description
|
||||||
notes = s.Notes
|
Notes = s.Notes
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,51 +190,51 @@ module ProfileForm =
|
||||||
[<CLIMutable>]
|
[<CLIMutable>]
|
||||||
type ProfileSearch =
|
type ProfileSearch =
|
||||||
{ /// Retrieve citizens from this continent
|
{ /// Retrieve citizens from this continent
|
||||||
continentId : string option
|
ContinentId : string option
|
||||||
|
|
||||||
/// Text for a search within a citizen's skills
|
/// Text for a search within a citizen's skills
|
||||||
skill : string option
|
Skill : string option
|
||||||
|
|
||||||
/// Text for a search with a citizen's professional biography and experience fields
|
/// Text for a search with a citizen's professional biography and experience fields
|
||||||
bioExperience : string option
|
BioExperience : string option
|
||||||
|
|
||||||
/// Whether to retrieve citizens who do or do not want remote work
|
/// Whether to retrieve citizens who do or do not want remote work
|
||||||
remoteWork : string
|
RemoteWork : string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// A user matching the profile search
|
/// A user matching the profile search
|
||||||
type ProfileSearchResult =
|
type ProfileSearchResult =
|
||||||
{ /// The ID of the citizen
|
{ /// The ID of the citizen
|
||||||
citizenId : CitizenId
|
CitizenId : CitizenId
|
||||||
|
|
||||||
/// The citizen's display name
|
/// The citizen's display name
|
||||||
displayName : string
|
DisplayName : string
|
||||||
|
|
||||||
/// Whether this citizen is currently seeking employment
|
/// Whether this citizen is currently seeking employment
|
||||||
seekingEmployment : bool
|
SeekingEmployment : bool
|
||||||
|
|
||||||
/// Whether this citizen is looking for remote work
|
/// Whether this citizen is looking for remote work
|
||||||
remoteWork : bool
|
RemoteWork : bool
|
||||||
|
|
||||||
/// Whether this citizen is looking for full-time work
|
/// Whether this citizen is looking for full-time work
|
||||||
fullTime : bool
|
FullTime : bool
|
||||||
|
|
||||||
/// When this profile was last updated
|
/// When this profile was last updated
|
||||||
lastUpdatedOn : Instant
|
LastUpdatedOn : Instant
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// The data required to show a viewable profile
|
/// The data required to show a viewable profile
|
||||||
type ProfileForView =
|
type ProfileForView =
|
||||||
{ /// The profile itself
|
{ /// The profile itself
|
||||||
profile : Profile
|
Profile : Profile
|
||||||
|
|
||||||
/// The citizen to whom the profile belongs
|
/// The citizen to whom the profile belongs
|
||||||
citizen : Citizen
|
Citizen : Citizen
|
||||||
|
|
||||||
/// The continent for the profile
|
/// The continent for the profile
|
||||||
continent : Continent
|
Continent : Continent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -251,25 +242,26 @@ type ProfileForView =
|
||||||
[<CLIMutable>]
|
[<CLIMutable>]
|
||||||
type PublicSearch =
|
type PublicSearch =
|
||||||
{ /// Retrieve citizens from this continent
|
{ /// Retrieve citizens from this continent
|
||||||
continentId : string option
|
ContinentId : string option
|
||||||
|
|
||||||
/// Retrieve citizens from this region
|
/// Retrieve citizens from this region
|
||||||
region : string option
|
Region : string option
|
||||||
|
|
||||||
/// Text for a search within a citizen's skills
|
/// Text for a search within a citizen's skills
|
||||||
skill : string option
|
Skill : string option
|
||||||
|
|
||||||
/// Whether to retrieve citizens who do or do not want remote work
|
/// Whether to retrieve citizens who do or do not want remote work
|
||||||
remoteWork : string
|
RemoteWork : string
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Support functions for public searches
|
/// Support functions for public searches
|
||||||
module PublicSearch =
|
module PublicSearch =
|
||||||
/// Is the search empty?
|
/// Is the search empty?
|
||||||
let isEmptySearch (search : PublicSearch) =
|
let isEmptySearch (search : PublicSearch) =
|
||||||
[ search.continentId
|
[ search.ContinentId
|
||||||
search.skill
|
search.Region
|
||||||
match search.remoteWork with "" -> Some search.remoteWork | _ -> None
|
search.Skill
|
||||||
|
if search.RemoteWork = "" then Some search.RemoteWork else None
|
||||||
]
|
]
|
||||||
|> List.exists Option.isSome
|
|> List.exists Option.isSome
|
||||||
|
|
||||||
|
@ -277,49 +269,49 @@ module PublicSearch =
|
||||||
/// A public profile search result
|
/// A public profile search result
|
||||||
type PublicSearchResult =
|
type PublicSearchResult =
|
||||||
{ /// The name of the continent on which the citizen resides
|
{ /// The name of the continent on which the citizen resides
|
||||||
continent : string
|
Continent : string
|
||||||
|
|
||||||
/// The region in which the citizen resides
|
/// The region in which the citizen resides
|
||||||
region : string
|
Region : string
|
||||||
|
|
||||||
/// Whether this citizen is seeking remote work
|
/// Whether this citizen is seeking remote work
|
||||||
remoteWork : bool
|
RemoteWork : bool
|
||||||
|
|
||||||
/// The skills this citizen has identified
|
/// The skills this citizen has identified
|
||||||
skills : string list
|
Skills : string list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// The data required to provide a success story
|
/// The data required to provide a success story
|
||||||
type StoryForm =
|
type StoryForm =
|
||||||
{ /// The ID of this story
|
{ /// The ID of this story
|
||||||
id : string
|
Id : string
|
||||||
|
|
||||||
/// Whether the employment was obtained from Jobs, Jobs, Jobs
|
/// Whether the employment was obtained from Jobs, Jobs, Jobs
|
||||||
fromHere : bool
|
FromHere : bool
|
||||||
|
|
||||||
/// The success story
|
/// The success story
|
||||||
story : string
|
Story : string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// An entry in the list of success stories
|
/// An entry in the list of success stories
|
||||||
type StoryEntry =
|
type StoryEntry =
|
||||||
{ /// The ID of this success story
|
{ /// The ID of this success story
|
||||||
id : SuccessId
|
Id : SuccessId
|
||||||
|
|
||||||
/// The ID of the citizen who recorded this story
|
/// The ID of the citizen who recorded this story
|
||||||
citizenId : CitizenId
|
CitizenId : CitizenId
|
||||||
|
|
||||||
/// The name of the citizen who recorded this story
|
/// The name of the citizen who recorded this story
|
||||||
citizenName : string
|
CitizenName : string
|
||||||
|
|
||||||
/// When this story was recorded
|
/// When this story was recorded
|
||||||
recordedOn : Instant
|
RecordedOn : Instant
|
||||||
|
|
||||||
/// Whether this story involves an opportunity that arose due to Jobs, Jobs, Jobs
|
/// Whether this story involves an opportunity that arose due to Jobs, Jobs, Jobs
|
||||||
fromHere : bool
|
FromHere : bool
|
||||||
|
|
||||||
/// Whether this report has a further story, or if it is simply a "found work" entry
|
/// Whether this report has a further story, or if it is simply a "found work" entry
|
||||||
hasStory : bool
|
HasStory : bool
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,10 +78,23 @@ module MarkdownString =
|
||||||
let toString = function Text text -> text
|
let toString = function Text text -> text
|
||||||
|
|
||||||
|
|
||||||
|
/// Types of contacts supported by Jobs, Jobs, Jobs
|
||||||
|
type ContactType =
|
||||||
|
/// E-mail addresses
|
||||||
|
| Email
|
||||||
|
/// Phone numbers (home, work, cell, etc.)
|
||||||
|
| Phone
|
||||||
|
/// Websites (personal, social, etc.)
|
||||||
|
| Website
|
||||||
|
|
||||||
|
|
||||||
/// Another way to contact a citizen from this site
|
/// Another way to contact a citizen from this site
|
||||||
type OtherContact =
|
type OtherContact =
|
||||||
{ /// The name of the contact (Email, No Agenda Social, LinkedIn, etc.)
|
{ /// The type of contact
|
||||||
Name : string
|
ContactType : ContactType
|
||||||
|
|
||||||
|
/// The name of the contact (Email, No Agenda Social, LinkedIn, etc.)
|
||||||
|
Name : string option
|
||||||
|
|
||||||
/// The value for the contact (e-mail address, user name, URL, etc.)
|
/// The value for the contact (e-mail address, user name, URL, etc.)
|
||||||
Value : string
|
Value : string
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
open NodaTime
|
open NodaTime
|
||||||
open System
|
open System
|
||||||
|
|
||||||
// fsharplint:disable FieldNames
|
|
||||||
|
|
||||||
/// A user of Jobs, Jobs, Jobs; a citizen of Gitmo Nation
|
/// A user of Jobs, Jobs, Jobs; a citizen of Gitmo Nation
|
||||||
[<CLIMutable; NoComparison; NoEquality>]
|
[<CLIMutable; NoComparison; NoEquality>]
|
||||||
type Citizen =
|
type Citizen =
|
||||||
|
|
|
@ -144,26 +144,26 @@ module Citizen =
|
||||||
let confirmToken : HttpHandler = fun next ctx -> task {
|
let confirmToken : HttpHandler = fun next ctx -> task {
|
||||||
let! form = ctx.BindJsonAsync<{| token : string |}> ()
|
let! form = ctx.BindJsonAsync<{| token : string |}> ()
|
||||||
let! valid = Citizens.confirmAccount form.token
|
let! valid = Citizens.confirmAccount form.token
|
||||||
return! json {| valid = valid |} next ctx
|
return! json {| Valid = valid |} next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// DELETE: /api/citizen/deny
|
// DELETE: /api/citizen/deny
|
||||||
let denyToken : HttpHandler = fun next ctx -> task {
|
let denyToken : HttpHandler = fun next ctx -> task {
|
||||||
let! form = ctx.BindJsonAsync<{| token : string |}> ()
|
let! form = ctx.BindJsonAsync<{| token : string |}> ()
|
||||||
let! valid = Citizens.denyAccount form.token
|
let! valid = Citizens.denyAccount form.token
|
||||||
return! json {| valid = valid |} next ctx
|
return! json {| Valid = valid |} next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST: /api/citizen/log-on
|
// POST: /api/citizen/log-on
|
||||||
let logOn : HttpHandler = fun next ctx -> task {
|
let logOn : HttpHandler = fun next ctx -> task {
|
||||||
let! form = ctx.BindJsonAsync<LogOnForm> ()
|
let! form = ctx.BindJsonAsync<LogOnForm> ()
|
||||||
match! Citizens.tryLogOn form.email form.password Auth.Passwords.verify Auth.Passwords.hash (now ctx) with
|
match! Citizens.tryLogOn form.Email form.Password Auth.Passwords.verify Auth.Passwords.hash (now ctx) with
|
||||||
| Ok citizen ->
|
| Ok citizen ->
|
||||||
return!
|
return!
|
||||||
json
|
json
|
||||||
{ jwt = Auth.createJwt citizen (authConfig ctx)
|
{ Jwt = Auth.createJwt citizen (authConfig ctx)
|
||||||
citizenId = CitizenId.toString citizen.Id
|
CitizenId = CitizenId.toString citizen.Id
|
||||||
name = Citizen.name citizen
|
Name = Citizen.name citizen
|
||||||
} next ctx
|
} next ctx
|
||||||
| Error msg -> return! RequestErrors.BAD_REQUEST msg next ctx
|
| Error msg -> return! RequestErrors.BAD_REQUEST msg next ctx
|
||||||
}
|
}
|
||||||
|
@ -228,14 +228,14 @@ module Listing =
|
||||||
Id = ListingId.create ()
|
Id = ListingId.create ()
|
||||||
CitizenId = currentCitizenId ctx
|
CitizenId = currentCitizenId ctx
|
||||||
CreatedOn = now
|
CreatedOn = now
|
||||||
Title = form.title
|
Title = form.Title
|
||||||
ContinentId = ContinentId.ofString form.continentId
|
ContinentId = ContinentId.ofString form.ContinentId
|
||||||
Region = form.region
|
Region = form.Region
|
||||||
IsRemote = form.remoteWork
|
IsRemote = form.RemoteWork
|
||||||
IsExpired = false
|
IsExpired = false
|
||||||
UpdatedOn = now
|
UpdatedOn = now
|
||||||
Text = Text form.text
|
Text = Text form.Text
|
||||||
NeededBy = (form.neededBy |> Option.map parseDate)
|
NeededBy = (form.NeededBy |> Option.map parseDate)
|
||||||
WasFilledHere = None
|
WasFilledHere = None
|
||||||
IsLegacy = false
|
IsLegacy = false
|
||||||
}
|
}
|
||||||
|
@ -250,12 +250,12 @@ module Listing =
|
||||||
let! form = ctx.BindJsonAsync<ListingForm> ()
|
let! form = ctx.BindJsonAsync<ListingForm> ()
|
||||||
do! Listings.save
|
do! Listings.save
|
||||||
{ listing with
|
{ listing with
|
||||||
Title = form.title
|
Title = form.Title
|
||||||
ContinentId = ContinentId.ofString form.continentId
|
ContinentId = ContinentId.ofString form.ContinentId
|
||||||
Region = form.region
|
Region = form.Region
|
||||||
IsRemote = form.remoteWork
|
IsRemote = form.RemoteWork
|
||||||
Text = Text form.text
|
Text = Text form.Text
|
||||||
NeededBy = form.neededBy |> Option.map parseDate
|
NeededBy = form.NeededBy |> Option.map parseDate
|
||||||
UpdatedOn = now ctx
|
UpdatedOn = now ctx
|
||||||
}
|
}
|
||||||
return! ok next ctx
|
return! ok next ctx
|
||||||
|
@ -272,16 +272,16 @@ module Listing =
|
||||||
do! Listings.save
|
do! Listings.save
|
||||||
{ listing with
|
{ listing with
|
||||||
IsExpired = true
|
IsExpired = true
|
||||||
WasFilledHere = Some form.fromHere
|
WasFilledHere = Some form.FromHere
|
||||||
UpdatedOn = now
|
UpdatedOn = now
|
||||||
}
|
}
|
||||||
match form.successStory with
|
match form.SuccessStory with
|
||||||
| Some storyText ->
|
| Some storyText ->
|
||||||
do! Successes.save
|
do! Successes.save
|
||||||
{ Id = SuccessId.create()
|
{ Id = SuccessId.create()
|
||||||
CitizenId = currentCitizenId ctx
|
CitizenId = currentCitizenId ctx
|
||||||
RecordedOn = now
|
RecordedOn = now
|
||||||
IsFromHere = form.fromHere
|
IsFromHere = form.FromHere
|
||||||
Source = "listing"
|
Source = "listing"
|
||||||
Story = (Text >> Some) storyText
|
Story = (Text >> Some) storyText
|
||||||
}
|
}
|
||||||
|
@ -328,7 +328,7 @@ module Profile =
|
||||||
// GET: /api/profile/count
|
// GET: /api/profile/count
|
||||||
let count : HttpHandler = authorize >=> fun next ctx -> task {
|
let count : HttpHandler = authorize >=> fun next ctx -> task {
|
||||||
let! theCount = Profiles.count ()
|
let! theCount = Profiles.count ()
|
||||||
return! json { count = theCount } next ctx
|
return! json {| Count = theCount |} next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST: /api/profile/save
|
// POST: /api/profile/save
|
||||||
|
@ -342,21 +342,21 @@ module Profile =
|
||||||
}
|
}
|
||||||
do! Profiles.save
|
do! Profiles.save
|
||||||
{ profile with
|
{ profile with
|
||||||
IsSeekingEmployment = form.isSeekingEmployment
|
IsSeekingEmployment = form.IsSeekingEmployment
|
||||||
IsPubliclySearchable = form.isPublic
|
IsPubliclySearchable = form.IsPublic
|
||||||
ContinentId = ContinentId.ofString form.continentId
|
ContinentId = ContinentId.ofString form.ContinentId
|
||||||
Region = form.region
|
Region = form.Region
|
||||||
IsRemote = form.remoteWork
|
IsRemote = form.RemoteWork
|
||||||
IsFullTime = form.fullTime
|
IsFullTime = form.FullTime
|
||||||
Biography = Text form.biography
|
Biography = Text form.Biography
|
||||||
LastUpdatedOn = now ctx
|
LastUpdatedOn = now ctx
|
||||||
Experience = noneIfBlank form.experience |> Option.map Text
|
Experience = noneIfBlank form.Experience |> Option.map Text
|
||||||
Skills = form.skills
|
Skills = form.Skills
|
||||||
|> List.map (fun s ->
|
|> List.map (fun s ->
|
||||||
{ Id = if s.id.StartsWith "new" then SkillId.create ()
|
{ Id = if s.Id.StartsWith "new" then SkillId.create ()
|
||||||
else SkillId.ofString s.id
|
else SkillId.ofString s.Id
|
||||||
Description = s.description
|
Description = s.Description
|
||||||
Notes = noneIfBlank s.notes
|
Notes = noneIfBlank s.Notes
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return! ok next ctx
|
return! ok next ctx
|
||||||
|
@ -414,21 +414,21 @@ module Success =
|
||||||
let citizenId = currentCitizenId ctx
|
let citizenId = currentCitizenId ctx
|
||||||
let! form = ctx.BindJsonAsync<StoryForm> ()
|
let! form = ctx.BindJsonAsync<StoryForm> ()
|
||||||
let! success = task {
|
let! success = task {
|
||||||
match form.id with
|
match form.Id with
|
||||||
| "new" ->
|
| "new" ->
|
||||||
return Some { Id = SuccessId.create ()
|
return Some { Id = SuccessId.create ()
|
||||||
CitizenId = citizenId
|
CitizenId = citizenId
|
||||||
RecordedOn = now ctx
|
RecordedOn = now ctx
|
||||||
IsFromHere = form.fromHere
|
IsFromHere = form.FromHere
|
||||||
Source = "profile"
|
Source = "profile"
|
||||||
Story = noneIfEmpty form.story |> Option.map Text
|
Story = noneIfEmpty form.Story |> Option.map Text
|
||||||
}
|
}
|
||||||
| successId ->
|
| successId ->
|
||||||
match! Successes.findById (SuccessId.ofString successId) with
|
match! Successes.findById (SuccessId.ofString successId) with
|
||||||
| Some story when story.CitizenId = citizenId ->
|
| Some story when story.CitizenId = citizenId ->
|
||||||
return Some { story with
|
return Some { story with
|
||||||
IsFromHere = form.fromHere
|
IsFromHere = form.FromHere
|
||||||
Story = noneIfEmpty form.story |> Option.map Text
|
Story = noneIfEmpty form.Story |> Option.map Text
|
||||||
}
|
}
|
||||||
| Some _ | None -> return None
|
| Some _ | None -> return None
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user