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