Version 3 #40
|
@ -17,16 +17,20 @@ let private fromShortGuid (it : string) =
|
||||||
|
|
||||||
/// Support functions for citizen IDs
|
/// Support functions for citizen IDs
|
||||||
module CitizenId =
|
module CitizenId =
|
||||||
|
|
||||||
/// Create a new citizen ID
|
/// Create a new citizen ID
|
||||||
let create () = (Guid.NewGuid >> CitizenId) ()
|
let create () = (Guid.NewGuid >> CitizenId) ()
|
||||||
|
|
||||||
/// A string representation of a citizen ID
|
/// A string representation of a citizen ID
|
||||||
let toString = function CitizenId it -> toShortGuid it
|
let toString = function CitizenId it -> toShortGuid it
|
||||||
|
|
||||||
/// Parse a string into a citizen ID
|
/// Parse a string into a citizen ID
|
||||||
let ofString = fromShortGuid >> CitizenId
|
let ofString = fromShortGuid >> CitizenId
|
||||||
|
|
||||||
|
|
||||||
/// Support functions for citizens
|
/// Support functions for citizens
|
||||||
module Citizen =
|
module Citizen =
|
||||||
|
|
||||||
/// Get the name of the citizen (the first of real name, display name, or handle that is filled in)
|
/// Get the name of the citizen (the first of real name, display name, or handle that is filled in)
|
||||||
let name x =
|
let name x =
|
||||||
[ x.realName; x.displayName; Some x.mastodonUser ]
|
[ x.realName; x.displayName; Some x.mastodonUser ]
|
||||||
|
@ -36,34 +40,46 @@ module Citizen =
|
||||||
|
|
||||||
/// Support functions for continent IDs
|
/// Support functions for continent IDs
|
||||||
module ContinentId =
|
module ContinentId =
|
||||||
|
|
||||||
/// Create a new continent ID
|
/// Create a new continent ID
|
||||||
let create () = (Guid.NewGuid >> ContinentId) ()
|
let create () = (Guid.NewGuid >> ContinentId) ()
|
||||||
|
|
||||||
/// A string representation of a continent ID
|
/// A string representation of a continent ID
|
||||||
let toString = function ContinentId it -> toShortGuid it
|
let toString = function ContinentId it -> toShortGuid it
|
||||||
|
|
||||||
/// Parse a string into a continent ID
|
/// Parse a string into a continent ID
|
||||||
let ofString = fromShortGuid >> ContinentId
|
let ofString = fromShortGuid >> ContinentId
|
||||||
|
|
||||||
|
|
||||||
/// Support functions for listing IDs
|
/// Support functions for listing IDs
|
||||||
module ListingId =
|
module ListingId =
|
||||||
|
|
||||||
/// Create a new job listing ID
|
/// Create a new job listing ID
|
||||||
let create () = (Guid.NewGuid >> ListingId) ()
|
let create () = (Guid.NewGuid >> ListingId) ()
|
||||||
|
|
||||||
/// A string representation of a listing ID
|
/// A string representation of a listing ID
|
||||||
let toString = function ListingId it -> toShortGuid it
|
let toString = function ListingId it -> toShortGuid it
|
||||||
|
|
||||||
/// Parse a string into a listing ID
|
/// Parse a string into a listing ID
|
||||||
let ofString = fromShortGuid >> ListingId
|
let ofString = fromShortGuid >> ListingId
|
||||||
|
|
||||||
|
|
||||||
/// Support functions for Markdown strings
|
/// Support functions for Markdown strings
|
||||||
module MarkdownString =
|
module MarkdownString =
|
||||||
|
|
||||||
/// The Markdown conversion pipeline (enables all advanced features)
|
/// The Markdown conversion pipeline (enables all advanced features)
|
||||||
let private pipeline = MarkdownPipelineBuilder().UseAdvancedExtensions().Build ()
|
let private pipeline = MarkdownPipelineBuilder().UseAdvancedExtensions().Build ()
|
||||||
|
|
||||||
/// Convert this Markdown string to HTML
|
/// Convert this Markdown string to HTML
|
||||||
let toHtml = function Text text -> Markdown.ToHtml (text, pipeline)
|
let toHtml = function Text text -> Markdown.ToHtml (text, pipeline)
|
||||||
|
|
||||||
|
/// Convert a Markdown string to its string representation
|
||||||
|
let toString = function Text text -> text
|
||||||
|
|
||||||
|
|
||||||
/// Support functions for Profiles
|
/// Support functions for Profiles
|
||||||
module Profile =
|
module Profile =
|
||||||
|
|
||||||
// An empty profile
|
// An empty profile
|
||||||
let empty =
|
let empty =
|
||||||
{ id = CitizenId Guid.Empty
|
{ id = CitizenId Guid.Empty
|
||||||
|
@ -79,21 +95,28 @@ module Profile =
|
||||||
skills = []
|
skills = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Support functions for skill IDs
|
/// Support functions for skill IDs
|
||||||
module SkillId =
|
module SkillId =
|
||||||
|
|
||||||
/// Create a new skill ID
|
/// Create a new skill ID
|
||||||
let create () = (Guid.NewGuid >> SkillId) ()
|
let create () = (Guid.NewGuid >> SkillId) ()
|
||||||
|
|
||||||
/// A string representation of a skill ID
|
/// A string representation of a skill ID
|
||||||
let toString = function SkillId it -> toShortGuid it
|
let toString = function SkillId it -> toShortGuid it
|
||||||
|
|
||||||
/// Parse a string into a skill ID
|
/// Parse a string into a skill ID
|
||||||
let ofString = fromShortGuid >> SkillId
|
let ofString = fromShortGuid >> SkillId
|
||||||
|
|
||||||
|
|
||||||
/// Support functions for success report IDs
|
/// Support functions for success report IDs
|
||||||
module SuccessId =
|
module SuccessId =
|
||||||
|
|
||||||
/// Create a new success report ID
|
/// Create a new success report ID
|
||||||
let create () = (Guid.NewGuid >> SuccessId) ()
|
let create () = (Guid.NewGuid >> SuccessId) ()
|
||||||
|
|
||||||
/// A string representation of a success report ID
|
/// A string representation of a success report ID
|
||||||
let toString = function SuccessId it -> toShortGuid it
|
let toString = function SuccessId it -> toShortGuid it
|
||||||
|
|
||||||
/// Parse a string into a success report ID
|
/// Parse a string into a success report ID
|
||||||
let ofString = fromShortGuid >> SuccessId
|
let ofString = fromShortGuid >> SuccessId
|
||||||
|
|
|
@ -11,16 +11,22 @@ open NodaTime
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -30,6 +36,7 @@ type ListingForm =
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -39,6 +46,7 @@ type ListingForView =
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -49,10 +57,13 @@ type ListingExpireForm =
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -62,8 +73,10 @@ type ListingSearch =
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -78,30 +91,41 @@ type Count =
|
||||||
|
|
||||||
/// An instance of a Mastodon server which is configured to work with Jobs, Jobs, Jobs
|
/// An instance of a Mastodon server which is configured to work with Jobs, Jobs, Jobs
|
||||||
type MastodonInstance () =
|
type MastodonInstance () =
|
||||||
|
|
||||||
/// The name of the instance
|
/// The name of the instance
|
||||||
member val Name = "" with get, set
|
member val Name = "" with get, set
|
||||||
|
|
||||||
/// The URL for this instance
|
/// The URL for this instance
|
||||||
member val Url = "" with get, set
|
member val Url = "" with get, set
|
||||||
|
|
||||||
/// The abbreviation used in the URL to distinguish this instance's return codes
|
/// The abbreviation used in the URL to distinguish this instance's return codes
|
||||||
member val Abbr = "" with get, set
|
member val Abbr = "" with get, set
|
||||||
|
|
||||||
/// The client ID (assigned by the Mastodon server)
|
/// The client ID (assigned by the Mastodon server)
|
||||||
member val ClientId = "" with get, set
|
member val ClientId = "" with get, set
|
||||||
|
|
||||||
/// The cryptographic secret (provided by the Mastodon server)
|
/// The cryptographic secret (provided by the Mastodon server)
|
||||||
member val Secret = "" with get, set
|
member val Secret = "" with get, set
|
||||||
|
|
||||||
/// Whether the instance is currently enabled
|
/// Whether the instance is currently enabled
|
||||||
member val IsEnabled = true with get, set
|
member val IsEnabled = true with get, set
|
||||||
|
|
||||||
/// If an instance is disabled, the reason for it being disabled
|
/// If an instance is disabled, the reason for it being disabled
|
||||||
member val Reason = "" with get, set
|
member val Reason = "" with get, set
|
||||||
|
|
||||||
|
|
||||||
/// The authorization options for Jobs, Jobs, Jobs
|
/// The authorization options for Jobs, Jobs, Jobs
|
||||||
type AuthOptions () =
|
type AuthOptions () =
|
||||||
|
|
||||||
/// The host for the return URL for Mastodon verification
|
/// The host for the return URL for Mastodon verification
|
||||||
member val ReturnHost = "" with get, set
|
member val ReturnHost = "" with get, set
|
||||||
|
|
||||||
/// The secret with which the server signs the JWTs for auth once we've verified with Mastodon
|
/// The secret with which the server signs the JWTs for auth once we've verified with Mastodon
|
||||||
member val ServerSecret = "" with get, set
|
member val ServerSecret = "" with get, set
|
||||||
|
|
||||||
/// The instances configured for use
|
/// The instances configured for use
|
||||||
member val Instances = Array.empty<MastodonInstance> with get, set
|
member val Instances = Array.empty<MastodonInstance> with get, set
|
||||||
|
|
||||||
interface IOptions<AuthOptions> with
|
interface IOptions<AuthOptions> with
|
||||||
override this.Value = this
|
override this.Value = this
|
||||||
|
|
||||||
|
@ -110,14 +134,19 @@ type AuthOptions () =
|
||||||
type Instance =
|
type Instance =
|
||||||
{ /// The name of the instance
|
{ /// The name of the instance
|
||||||
name : string
|
name : string
|
||||||
|
|
||||||
/// The URL for this instance
|
/// The URL for this instance
|
||||||
url : string
|
url : string
|
||||||
|
|
||||||
/// The abbreviation used in the URL to distinguish this instance's return codes
|
/// The abbreviation used in the URL to distinguish this instance's return codes
|
||||||
abbr : string
|
abbr : string
|
||||||
|
|
||||||
/// The client ID (assigned by the Mastodon server)
|
/// The client ID (assigned by the Mastodon server)
|
||||||
clientId : string
|
clientId : string
|
||||||
|
|
||||||
/// Whether this instance is currently enabled
|
/// Whether this instance is currently enabled
|
||||||
isEnabled : bool
|
isEnabled : bool
|
||||||
|
|
||||||
/// If not enabled, the reason the instance is disabled
|
/// If not enabled, the reason the instance is disabled
|
||||||
reason : string
|
reason : string
|
||||||
}
|
}
|
||||||
|
@ -127,8 +156,10 @@ type Instance =
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -138,28 +169,38 @@ type SkillForm =
|
||||||
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 user's real name
|
/// The user's real name
|
||||||
realName : string
|
realName : string
|
||||||
|
|
||||||
/// 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
|
||||||
module ProfileForm =
|
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 : Types.Profile) =
|
let fromProfile (profile : Types.Profile) =
|
||||||
{ isSeekingEmployment = profile.seekingEmployment
|
{ isSeekingEmployment = profile.seekingEmployment
|
||||||
|
@ -169,8 +210,8 @@ module ProfileForm =
|
||||||
region = profile.region
|
region = profile.region
|
||||||
remoteWork = profile.remoteWork
|
remoteWork = profile.remoteWork
|
||||||
fullTime = profile.fullTime
|
fullTime = profile.fullTime
|
||||||
biography = match profile.biography with Text bio -> bio
|
biography = MarkdownString.toString profile.biography
|
||||||
experience = profile.experience |> Option.map (fun x -> match x with Text exp -> exp)
|
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
|
||||||
|
@ -185,10 +226,13 @@ module ProfileForm =
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -198,14 +242,19 @@ type ProfileSearch =
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -215,8 +264,10 @@ type ProfileSearchResult =
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -227,10 +278,13 @@ type ProfileForView =
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -250,10 +304,13 @@ module PublicSearch =
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -263,8 +320,10 @@ type PublicSearchResult =
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -274,14 +333,19 @@ type StoryForm =
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,18 +14,25 @@ type CitizenId = CitizenId of Guid
|
||||||
type Citizen =
|
type Citizen =
|
||||||
{ /// The ID of the user
|
{ /// The ID of the user
|
||||||
id : CitizenId
|
id : CitizenId
|
||||||
|
|
||||||
/// The Mastodon instance abbreviation from which this citizen is authorized
|
/// The Mastodon instance abbreviation from which this citizen is authorized
|
||||||
instance : string
|
instance : string
|
||||||
|
|
||||||
/// The handle by which the user is known on Mastodon
|
/// The handle by which the user is known on Mastodon
|
||||||
mastodonUser : string
|
mastodonUser : string
|
||||||
|
|
||||||
/// The user's display name from Mastodon (updated every login)
|
/// The user's display name from Mastodon (updated every login)
|
||||||
displayName : string option
|
displayName : string option
|
||||||
|
|
||||||
/// The user's real name
|
/// The user's real name
|
||||||
realName : string option
|
realName : string option
|
||||||
|
|
||||||
/// The URL for the user's Mastodon profile
|
/// The URL for the user's Mastodon profile
|
||||||
profileUrl : string
|
profileUrl : string
|
||||||
|
|
||||||
/// When the user joined Jobs, Jobs, Jobs
|
/// When the user joined Jobs, Jobs, Jobs
|
||||||
joinedOn : Instant
|
joinedOn : Instant
|
||||||
|
|
||||||
/// When the user last logged in
|
/// When the user last logged in
|
||||||
lastSeenOn : Instant
|
lastSeenOn : Instant
|
||||||
}
|
}
|
||||||
|
@ -39,6 +46,7 @@ type ContinentId = ContinentId of Guid
|
||||||
type Continent =
|
type Continent =
|
||||||
{ /// The ID of the continent
|
{ /// The ID of the continent
|
||||||
id : ContinentId
|
id : ContinentId
|
||||||
|
|
||||||
/// The name of the continent
|
/// The name of the continent
|
||||||
name : string
|
name : string
|
||||||
}
|
}
|
||||||
|
@ -56,26 +64,37 @@ type ListingId = ListingId of Guid
|
||||||
type Listing =
|
type Listing =
|
||||||
{ /// The ID of the job listing
|
{ /// The ID of the job listing
|
||||||
id : ListingId
|
id : ListingId
|
||||||
|
|
||||||
/// The ID of the citizen who posted the job listing
|
/// The ID of the citizen who posted the job listing
|
||||||
citizenId : CitizenId
|
citizenId : CitizenId
|
||||||
|
|
||||||
/// When this job listing was created
|
/// When this job listing was created
|
||||||
createdOn : Instant
|
createdOn : Instant
|
||||||
|
|
||||||
/// The short title of the job listing
|
/// The short title of the job listing
|
||||||
title : string
|
title : string
|
||||||
|
|
||||||
/// The ID of the continent on which the job is located
|
/// The ID of the continent on which the job is located
|
||||||
continentId : ContinentId
|
continentId : ContinentId
|
||||||
|
|
||||||
/// The region in which the job is located
|
/// The region in which the job is located
|
||||||
region : string
|
region : string
|
||||||
|
|
||||||
/// Whether this listing is for remote work
|
/// Whether this listing is for remote work
|
||||||
remoteWork : bool
|
remoteWork : bool
|
||||||
|
|
||||||
/// Whether this listing has expired
|
/// Whether this listing has expired
|
||||||
isExpired : bool
|
isExpired : bool
|
||||||
|
|
||||||
/// When this listing was last updated
|
/// When this listing was last updated
|
||||||
updatedOn : Instant
|
updatedOn : Instant
|
||||||
|
|
||||||
/// The details of this job
|
/// The details of this job
|
||||||
text : MarkdownString
|
text : MarkdownString
|
||||||
|
|
||||||
/// When this job needs to be filled
|
/// When this job needs to be filled
|
||||||
neededBy : LocalDate option
|
neededBy : LocalDate option
|
||||||
|
|
||||||
/// Was this job filled as part of its appearance on Jobs, Jobs, Jobs?
|
/// Was this job filled as part of its appearance on Jobs, Jobs, Jobs?
|
||||||
wasFilledHere : bool option
|
wasFilledHere : bool option
|
||||||
}
|
}
|
||||||
|
@ -88,8 +107,10 @@ type SkillId = SkillId of Guid
|
||||||
type Skill =
|
type Skill =
|
||||||
{ /// The ID of the skill
|
{ /// The ID of the skill
|
||||||
id : SkillId
|
id : SkillId
|
||||||
|
|
||||||
/// A description of the skill
|
/// A description of the skill
|
||||||
description : string
|
description : string
|
||||||
|
|
||||||
/// Notes regarding this skill (level, duration, etc.)
|
/// Notes regarding this skill (level, duration, etc.)
|
||||||
notes : string option
|
notes : string option
|
||||||
}
|
}
|
||||||
|
@ -100,24 +121,34 @@ type Skill =
|
||||||
type Profile =
|
type Profile =
|
||||||
{ /// The ID of the citizen to whom this profile belongs
|
{ /// The ID of the citizen to whom this profile belongs
|
||||||
id : CitizenId
|
id : CitizenId
|
||||||
|
|
||||||
/// Whether this citizen is actively seeking employment
|
/// Whether this citizen is actively seeking employment
|
||||||
seekingEmployment : bool
|
seekingEmployment : bool
|
||||||
|
|
||||||
/// Whether this citizen allows their profile to be a part of the publicly-viewable, anonymous data
|
/// Whether this citizen allows their profile to be a part of the publicly-viewable, anonymous data
|
||||||
isPublic : bool
|
isPublic : bool
|
||||||
|
|
||||||
/// The ID of the continent on which the citizen resides
|
/// The ID of the continent on which the citizen resides
|
||||||
continentId : ContinentId
|
continentId : ContinentId
|
||||||
|
|
||||||
/// The region in which the citizen resides
|
/// The region in which the citizen resides
|
||||||
region : string
|
region : string
|
||||||
|
|
||||||
/// Whether the citizen is looking for remote work
|
/// Whether the citizen is looking for remote work
|
||||||
remoteWork : bool
|
remoteWork : bool
|
||||||
|
|
||||||
/// Whether the citizen is looking for full-time work
|
/// Whether the citizen is looking for full-time work
|
||||||
fullTime : bool
|
fullTime : bool
|
||||||
|
|
||||||
/// The citizen's professional biography
|
/// The citizen's professional biography
|
||||||
biography : MarkdownString
|
biography : MarkdownString
|
||||||
|
|
||||||
/// When the citizen last updated their profile
|
/// When the citizen last updated their profile
|
||||||
lastUpdatedOn : Instant
|
lastUpdatedOn : Instant
|
||||||
|
|
||||||
/// The citizen's experience (topical / chronological)
|
/// The citizen's experience (topical / chronological)
|
||||||
experience : MarkdownString option
|
experience : MarkdownString option
|
||||||
|
|
||||||
/// Skills this citizen possesses
|
/// Skills this citizen possesses
|
||||||
skills : Skill list
|
skills : Skill list
|
||||||
}
|
}
|
||||||
|
@ -130,14 +161,19 @@ type SuccessId = SuccessId of Guid
|
||||||
type Success =
|
type Success =
|
||||||
{ /// The ID of the success report
|
{ /// The ID of the success report
|
||||||
id : SuccessId
|
id : SuccessId
|
||||||
|
|
||||||
/// The ID of the citizen who wrote this success report
|
/// The ID of the citizen who wrote this success report
|
||||||
citizenId : CitizenId
|
citizenId : CitizenId
|
||||||
|
|
||||||
/// When this success report was recorded
|
/// When this success report was recorded
|
||||||
recordedOn : Instant
|
recordedOn : Instant
|
||||||
|
|
||||||
/// Whether the success was due, at least in part, to Jobs, Jobs, Jobs
|
/// Whether the success was due, at least in part, to Jobs, Jobs, Jobs
|
||||||
fromHere : bool
|
fromHere : bool
|
||||||
|
|
||||||
/// The source of this success (listing or profile)
|
/// The source of this success (listing or profile)
|
||||||
source : string
|
source : string
|
||||||
|
|
||||||
/// The success story
|
/// The success story
|
||||||
story : MarkdownString option
|
story : MarkdownString option
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,8 +31,7 @@ module Converters =
|
||||||
type MarkdownStringJsonConverter() =
|
type MarkdownStringJsonConverter() =
|
||||||
inherit JsonConverter<MarkdownString>()
|
inherit JsonConverter<MarkdownString>()
|
||||||
override _.WriteJson(writer : JsonWriter, value : MarkdownString, _ : JsonSerializer) =
|
override _.WriteJson(writer : JsonWriter, value : MarkdownString, _ : JsonSerializer) =
|
||||||
let (Text text) = value
|
writer.WriteValue (MarkdownString.toString value)
|
||||||
writer.WriteValue text
|
|
||||||
override _.ReadJson(reader: JsonReader, _ : Type, _ : MarkdownString, _ : bool, _ : JsonSerializer) =
|
override _.ReadJson(reader: JsonReader, _ : Type, _ : MarkdownString, _ : bool, _ : JsonSerializer) =
|
||||||
(string >> Text) reader.Value
|
(string >> Text) reader.Value
|
||||||
|
|
||||||
|
@ -75,16 +74,22 @@ module Converters =
|
||||||
/// Table names
|
/// Table names
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module Table =
|
module Table =
|
||||||
|
|
||||||
/// The user (citizen of Gitmo Nation) table
|
/// The user (citizen of Gitmo Nation) table
|
||||||
let Citizen = "citizen"
|
let Citizen = "citizen"
|
||||||
|
|
||||||
/// The continent table
|
/// The continent table
|
||||||
let Continent = "continent"
|
let Continent = "continent"
|
||||||
|
|
||||||
/// The job listing table
|
/// The job listing table
|
||||||
let Listing = "listing"
|
let Listing = "listing"
|
||||||
|
|
||||||
/// The citizen employment profile table
|
/// The citizen employment profile table
|
||||||
let Profile = "profile"
|
let Profile = "profile"
|
||||||
|
|
||||||
/// The success story table
|
/// The success story table
|
||||||
let Success = "success"
|
let Success = "success"
|
||||||
|
|
||||||
/// All tables
|
/// All tables
|
||||||
let all () = [ Citizen; Continent; Listing; Profile; Success ]
|
let all () = [ Citizen; Continent; Listing; Profile; Success ]
|
||||||
|
|
||||||
|
@ -166,7 +171,7 @@ module Startup =
|
||||||
let! userIdx = fromTable Table.Citizen |> indexList |> result<string list> conn
|
let! userIdx = fromTable Table.Citizen |> indexList |> result<string list> conn
|
||||||
if not (List.contains "instanceUser" userIdx) then
|
if not (List.contains "instanceUser" userIdx) then
|
||||||
do! fromTable Table.Citizen
|
do! fromTable Table.Citizen
|
||||||
|> indexCreateFunc "instanceUser" (fun row -> r.Array (row.G "instance", row.G "mastodonUser"))
|
|> indexCreateFunc "instanceUser" (fun row -> [| row.G "instance"; row.G "mastodonUser" |])
|
||||||
|> write conn
|
|> write conn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,7 +180,21 @@ open JobsJobsJobs.Domain
|
||||||
open JobsJobsJobs.Domain.SharedTypes
|
open JobsJobsJobs.Domain.SharedTypes
|
||||||
|
|
||||||
/// Sanitize user input, and create a "contains" pattern for use with RethinkDB queries
|
/// Sanitize user input, and create a "contains" pattern for use with RethinkDB queries
|
||||||
let regexContains = System.Text.RegularExpressions.Regex.Escape >> sprintf "(?i)%s"
|
let private regexContains = System.Text.RegularExpressions.Regex.Escape >> sprintf "(?i)%s"
|
||||||
|
|
||||||
|
/// Apply filters to a query, ensuring that types all match up
|
||||||
|
let private applyFilters (filters : (ReqlExpr -> Filter) list) query : ReqlExpr =
|
||||||
|
if List.isEmpty filters then
|
||||||
|
query
|
||||||
|
else
|
||||||
|
let first = List.head filters query
|
||||||
|
List.fold (fun q (f : ReqlExpr -> Filter) -> f q) first (List.tail filters)
|
||||||
|
|
||||||
|
/// Derive a user's display name from real name, display name, or handle (in that order)
|
||||||
|
let private deriveDisplayName (it : ReqlExpr) =
|
||||||
|
r.Branch (it.G("realName" ).Default_("").Ne "", it.G "realName",
|
||||||
|
it.G("displayName").Default_("").Ne "", it.G "displayName",
|
||||||
|
it.G "mastodonUser")
|
||||||
|
|
||||||
/// Profile data access functions
|
/// Profile data access functions
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
|
@ -209,75 +228,62 @@ module Profile =
|
||||||
|
|
||||||
/// Search profiles (logged-on users)
|
/// Search profiles (logged-on users)
|
||||||
let search (search : ProfileSearch) conn =
|
let search (search : ProfileSearch) conn =
|
||||||
(seq<ReqlExpr -> ReqlExpr> {
|
fromTable Table.Profile
|
||||||
match search.continentId with
|
|> eqJoin "id" (fromTable Table.Citizen)
|
||||||
| Some cId -> yield (fun q -> q.Filter (r.HashMap (nameof search.continentId, ContinentId.ofString cId)))
|
|> without [ "right.id" ]
|
||||||
|
|> zip
|
||||||
|
|> applyFilters
|
||||||
|
[ match search.continentId with
|
||||||
|
| Some contId -> yield filter {| continentId = ContinentId.ofString contId |}
|
||||||
| None -> ()
|
| None -> ()
|
||||||
match search.remoteWork with
|
match search.remoteWork with
|
||||||
| "" -> ()
|
| "" -> ()
|
||||||
| _ -> yield (fun q -> q.Filter (r.HashMap (nameof search.remoteWork, search.remoteWork = "yes")))
|
| _ -> yield filter {| remoteWork = search.remoteWork = "yes" |}
|
||||||
match search.skill with
|
match search.skill with
|
||||||
| Some skl ->
|
| Some skl ->
|
||||||
yield (fun q -> q.Filter (ReqlFunction1(fun it ->
|
yield filterFunc (fun it ->
|
||||||
it.G("skills").Contains (ReqlFunction1(fun s -> s.G("description").Match (regexContains skl))))))
|
it.G("skills").Contains (ReqlFunction1 (fun s -> s.G("description").Match (regexContains skl))))
|
||||||
| None -> ()
|
| None -> ()
|
||||||
match search.bioExperience with
|
match search.bioExperience with
|
||||||
| Some text ->
|
| Some text ->
|
||||||
let txt = regexContains text
|
let txt = regexContains text
|
||||||
yield (fun q -> q.Filter (ReqlFunction1(fun it ->
|
yield filterFunc (fun it -> it.G("biography").Match(txt).Or (it.G("experience").Match txt))
|
||||||
it.G("biography").Match(txt).Or (it.G("experience").Match txt))))
|
|
||||||
| None -> ()
|
| None -> ()
|
||||||
}
|
]
|
||||||
|> Seq.toList
|
|> mergeFunc (fun it -> {| displayName = deriveDisplayName it; citizenId = it.G "id" |})
|
||||||
|> List.fold
|
|
||||||
(fun q f -> f q)
|
|
||||||
(r.Table(Table.Profile)
|
|
||||||
.EqJoin("id", r.Table Table.Citizen)
|
|
||||||
.Without(r.HashMap ("right", "id"))
|
|
||||||
.Zip () :> ReqlExpr))
|
|
||||||
|> mergeFunc (fun it ->
|
|
||||||
r.HashMap("displayName",
|
|
||||||
r.Branch (it.G("realName" ).Default_("").Ne "", it.G "realName",
|
|
||||||
it.G("displayName").Default_("").Ne "", it.G "displayName",
|
|
||||||
it.G "mastodonUser"))
|
|
||||||
.With ("citizenId", it.G "id"))
|
|
||||||
|> pluck [ "citizenId"; "displayName"; "seekingEmployment"; "remoteWork"; "fullTime"; "lastUpdatedOn" ]
|
|> pluck [ "citizenId"; "displayName"; "seekingEmployment"; "remoteWork"; "fullTime"; "lastUpdatedOn" ]
|
||||||
|> orderByFunc (fun it -> it.G("displayName").Downcase ())
|
|> orderByFunc (fun it -> it.G("displayName").Downcase ())
|
||||||
|> result<ProfileSearchResult list> conn
|
|> result<ProfileSearchResult list> conn
|
||||||
|
|
||||||
// Search profiles (public)
|
// Search profiles (public)
|
||||||
let publicSearch (search : PublicSearch) conn =
|
let publicSearch (search : PublicSearch) conn =
|
||||||
(seq<ReqlExpr -> ReqlExpr> {
|
fromTable Table.Profile
|
||||||
|
|> eqJoin "continentId" (fromTable Table.Continent)
|
||||||
|
|> without [ "right.id" ]
|
||||||
|
|> zip
|
||||||
|
|> applyFilters
|
||||||
|
[ yield filter {| isPublic = true |}
|
||||||
match search.continentId with
|
match search.continentId with
|
||||||
| Some cId -> yield (fun q -> q.Filter (r.HashMap (nameof search.continentId, ContinentId.ofString cId)))
|
| Some contId -> yield filter {| continentId = ContinentId.ofString contId |}
|
||||||
| None -> ()
|
| None -> ()
|
||||||
match search.region with
|
match search.region with
|
||||||
| Some reg ->
|
| Some reg -> yield filterFunc (fun it -> it.G("region").Match (regexContains reg))
|
||||||
yield (fun q -> q.Filter (ReqlFunction1 (fun it -> upcast it.G("region").Match (regexContains reg))))
|
|
||||||
| None -> ()
|
| None -> ()
|
||||||
match search.remoteWork with
|
match search.remoteWork with
|
||||||
| "" -> ()
|
| "" -> ()
|
||||||
| _ -> yield (fun q -> q.Filter (r.HashMap (nameof search.remoteWork, search.remoteWork = "yes")))
|
| _ -> yield filter {| remoteWork = search.remoteWork = "yes" |}
|
||||||
match search.skill with
|
match search.skill with
|
||||||
| Some skl ->
|
| Some skl ->
|
||||||
yield (fun q -> q.Filter (ReqlFunction1 (fun it ->
|
yield filterFunc (fun it ->
|
||||||
it.G("skills").Contains (ReqlFunction1(fun s -> s.G("description").Match (regexContains skl))))))
|
it.G("skills").Contains (ReqlFunction1 (fun s -> s.G("description").Match (regexContains skl))))
|
||||||
| None -> ()
|
| None -> ()
|
||||||
}
|
]
|
||||||
|> Seq.toList
|
|
||||||
|> List.fold
|
|
||||||
(fun q f -> f q)
|
|
||||||
(r.Table(Table.Profile)
|
|
||||||
.EqJoin("continentId", r.Table Table.Continent)
|
|
||||||
.Without(r.HashMap ("right", "id"))
|
|
||||||
.Zip()
|
|
||||||
.Filter(r.HashMap ("isPublic", true))))
|
|
||||||
|> mergeFunc (fun it ->
|
|> mergeFunc (fun it ->
|
||||||
r.HashMap("skills",
|
{| skills = it.G("skills").Map (ReqlFunction1 (fun skill ->
|
||||||
it.G("skills").Map (ReqlFunction1 (fun skill ->
|
|
||||||
r.Branch(skill.G("notes").Default_("").Eq "", skill.G "description",
|
r.Branch(skill.G("notes").Default_("").Eq "", skill.G "description",
|
||||||
skill.G("description").Add(" (").Add(skill.G("notes")).Add ")"))))
|
skill.G("description").Add(" (").Add(skill.G("notes")).Add ")")))
|
||||||
.With("continent", it.G "name"))
|
continent = it.G "name"
|
||||||
|
|})
|
||||||
|> pluck [ "continent"; "region"; "skills"; "remoteWork" ]
|
|> pluck [ "continent"; "region"; "skills"; "remoteWork" ]
|
||||||
|> result<PublicSearchResult list> conn
|
|> result<PublicSearchResult list> conn
|
||||||
|
|
||||||
|
@ -295,7 +301,7 @@ module Citizen =
|
||||||
let findByMastodonUser (instance : string) (mastodonUser : string) conn = task {
|
let findByMastodonUser (instance : string) (mastodonUser : string) conn = task {
|
||||||
let! u =
|
let! u =
|
||||||
fromTable Table.Citizen
|
fromTable Table.Citizen
|
||||||
|> getAllWithIndex [ r.Array (instance, mastodonUser) ] "instanceUser"
|
|> getAllWithIndex [ [| instance; mastodonUser |] ] "instanceUser"
|
||||||
|> limit 1
|
|> limit 1
|
||||||
|> result<Citizen list> conn
|
|> result<Citizen list> conn
|
||||||
return List.tryHead u
|
return List.tryHead u
|
||||||
|
@ -311,8 +317,7 @@ module Citizen =
|
||||||
let logOnUpdate (citizen : Citizen) conn =
|
let logOnUpdate (citizen : Citizen) conn =
|
||||||
fromTable Table.Citizen
|
fromTable Table.Citizen
|
||||||
|> get citizen.id
|
|> get citizen.id
|
||||||
|> update (r.HashMap( nameof citizen.displayName, citizen.displayName)
|
|> update {| displayName = citizen.displayName; lastSeenOn = citizen.lastSeenOn |}
|
||||||
.With (nameof citizen.lastSeenOn, citizen.lastSeenOn))
|
|
||||||
|> write conn
|
|> write conn
|
||||||
|
|
||||||
/// Delete a citizen
|
/// Delete a citizen
|
||||||
|
@ -336,7 +341,7 @@ module Citizen =
|
||||||
let realNameUpdate (citizenId : CitizenId) (realName : string option) conn =
|
let realNameUpdate (citizenId : CitizenId) (realName : string option) conn =
|
||||||
fromTable Table.Citizen
|
fromTable Table.Citizen
|
||||||
|> get citizenId
|
|> get citizenId
|
||||||
|> update (r.HashMap (nameof realName, realName))
|
|> update {| realName = realName |}
|
||||||
|> write conn
|
|> write conn
|
||||||
|
|
||||||
|
|
||||||
|
@ -362,12 +367,15 @@ module Listing =
|
||||||
|
|
||||||
open NodaTime
|
open NodaTime
|
||||||
|
|
||||||
|
/// Convert a joined query to the form needed for ListingForView deserialization
|
||||||
|
let private toListingForView (it : ReqlExpr) : obj = {| listing = it.G "left"; continent = it.G "right" |}
|
||||||
|
|
||||||
/// Find all job listings posted by the given citizen
|
/// Find all job listings posted by the given citizen
|
||||||
let findByCitizen (citizenId : CitizenId) conn =
|
let findByCitizen (citizenId : CitizenId) conn =
|
||||||
fromTable Table.Listing
|
fromTable Table.Listing
|
||||||
|> getAllWithIndex [ citizenId ] (nameof citizenId)
|
|> getAllWithIndex [ citizenId ] (nameof citizenId)
|
||||||
|> eqJoin "continentId" (fromTable Table.Continent)
|
|> eqJoin "continentId" (fromTable Table.Continent)
|
||||||
|> mapFunc (fun it -> r.HashMap("listing", it.G "left").With ("continent", it.G "right"))
|
|> mapFunc toListingForView
|
||||||
|> result<ListingForView list> conn
|
|> result<ListingForView list> conn
|
||||||
|
|
||||||
/// Find a listing by its ID
|
/// Find a listing by its ID
|
||||||
|
@ -380,9 +388,9 @@ module Listing =
|
||||||
let findByIdForView (listingId : ListingId) conn = task {
|
let findByIdForView (listingId : ListingId) conn = task {
|
||||||
let! listing =
|
let! listing =
|
||||||
fromTable Table.Listing
|
fromTable Table.Listing
|
||||||
|> filter (r.HashMap ("id", listingId))
|
|> filter {| id = listingId |}
|
||||||
|> eqJoin "continentId" (fromTable Table.Continent)
|
|> eqJoin "continentId" (fromTable Table.Continent)
|
||||||
|> mapFunc (fun it -> r.HashMap("listing", it.G "left").With ("continent", it.G "right"))
|
|> mapFunc toListingForView
|
||||||
|> result<ListingForView list> conn
|
|> result<ListingForView list> conn
|
||||||
return List.tryHead listing
|
return List.tryHead listing
|
||||||
}
|
}
|
||||||
|
@ -404,36 +412,29 @@ module Listing =
|
||||||
let expire (listingId : ListingId) (fromHere : bool) (now : Instant) conn =
|
let expire (listingId : ListingId) (fromHere : bool) (now : Instant) conn =
|
||||||
(fromTable Table.Listing
|
(fromTable Table.Listing
|
||||||
|> get listingId)
|
|> get listingId)
|
||||||
.Update (r.HashMap("isExpired", true).With("wasFilledHere", fromHere).With ("updatedOn", now))
|
.Update {| isExpired = true; wasFilledHere = fromHere; updatedOn = now |}
|
||||||
|> write conn
|
|> write conn
|
||||||
|
|
||||||
/// Search job listings
|
/// Search job listings
|
||||||
let search (search : ListingSearch) conn =
|
let search (search : ListingSearch) conn =
|
||||||
(seq<ReqlExpr -> ReqlExpr> {
|
fromTable Table.Listing
|
||||||
match search.continentId with
|
|> getAllWithIndex [ false ] "isExpired"
|
||||||
| Some cId -> yield (fun q -> q.Filter (r.HashMap (nameof search.continentId, ContinentId.ofString cId)))
|
|> applyFilters
|
||||||
|
[ match search.continentId with
|
||||||
|
| Some contId -> yield filter {| continentId = ContinentId.ofString contId |}
|
||||||
| None -> ()
|
| None -> ()
|
||||||
match search.region with
|
match search.region with
|
||||||
| Some rgn ->
|
| Some rgn -> yield filterFunc (fun it -> it.G(nameof search.region).Match (regexContains rgn))
|
||||||
yield (fun q ->
|
|
||||||
q.Filter (ReqlFunction1 (fun it -> it.G(nameof search.region).Match (regexContains rgn))))
|
|
||||||
| None -> ()
|
| None -> ()
|
||||||
match search.remoteWork with
|
match search.remoteWork with
|
||||||
| "" -> ()
|
| "" -> ()
|
||||||
| _ -> yield (fun q -> q.Filter (r.HashMap (nameof search.remoteWork, search.remoteWork = "yes")))
|
| _ -> yield filter {| remoteWork = search.remoteWork = "yes" |}
|
||||||
match search.text with
|
match search.text with
|
||||||
| Some text ->
|
| Some text -> yield filterFunc (fun it -> it.G(nameof search.text).Match (regexContains text))
|
||||||
yield (fun q ->
|
|
||||||
q.Filter (ReqlFunction1 (fun it -> it.G(nameof search.text).Match (regexContains text))))
|
|
||||||
| None -> ()
|
| None -> ()
|
||||||
}
|
]
|
||||||
|> Seq.toList
|
|
||||||
|> List.fold
|
|
||||||
(fun q f -> f q)
|
|
||||||
(fromTable Table.Listing
|
|
||||||
|> getAllWithIndex [ false ] "isExpired" :> ReqlExpr))
|
|
||||||
|> eqJoin "continentId" (fromTable Table.Continent)
|
|> eqJoin "continentId" (fromTable Table.Continent)
|
||||||
|> mapFunc (fun it -> r.HashMap("listing", it.G "left").With ("continent", it.G "right"))
|
|> mapFunc toListingForView
|
||||||
|> result<ListingForView list> conn
|
|> result<ListingForView list> conn
|
||||||
|
|
||||||
|
|
||||||
|
@ -456,16 +457,11 @@ module Success =
|
||||||
|
|
||||||
// Retrieve all success stories
|
// Retrieve all success stories
|
||||||
let all conn =
|
let all conn =
|
||||||
(fromTable Table.Success
|
fromTable Table.Success
|
||||||
|> eqJoin "citizenId" (fromTable Table.Citizen))
|
|> eqJoin "citizenId" (fromTable Table.Citizen)
|
||||||
.Without(r.HashMap ("right", "id"))
|
|> without [ "right.id" ]
|
||||||
|> zip
|
|> zip
|
||||||
|> mergeFunc (fun it ->
|
|> mergeFunc (fun it -> {| citizenName = deriveDisplayName it; hasStory = it.G("story").Default_("").Gt "" |})
|
||||||
r.HashMap("citizenName",
|
|
||||||
r.Branch(it.G("realName" ).Default_("").Ne "", it.G "realName",
|
|
||||||
it.G("displayName").Default_("").Ne "", it.G "displayName",
|
|
||||||
it.G "mastodonUser"))
|
|
||||||
.With ("hasStory", it.G("story").Default_("").Gt ""))
|
|
||||||
|> pluck [ "id"; "citizenId"; "citizenName"; "recordedOn"; "fromHere"; "hasStory" ]
|
|> pluck [ "id"; "citizenId"; "citizenName"; "recordedOn"; "fromHere"; "hasStory" ]
|
||||||
|> orderByDescending "recordedOn"
|
|> orderByDescending "recordedOn"
|
||||||
|> result<StoryEntry list> conn
|
|> result<StoryEntry list> conn
|
||||||
|
|
Loading…
Reference in New Issue
Block a user