@@ -25,6 +25,34 @@ module CitizenId =
|
||||
let value = function CitizenId guid -> guid
|
||||
|
||||
|
||||
/// 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
|
||||
|
||||
/// Functions to support contact types
|
||||
module ContactType =
|
||||
|
||||
/// Parse a contact type from a string
|
||||
let parse typ =
|
||||
match typ with
|
||||
| "Email" -> Email
|
||||
| "Phone" -> Phone
|
||||
| "Website" -> Website
|
||||
| it -> invalidOp $"{it} is not a valid contact type"
|
||||
|
||||
/// Convert a contact type to its string representation
|
||||
let toString =
|
||||
function
|
||||
| Email -> "Email"
|
||||
| Phone -> "Phone"
|
||||
| Website -> "Website"
|
||||
|
||||
|
||||
/// The ID of a continent
|
||||
type ContinentId = ContinentId of Guid
|
||||
|
||||
@@ -112,34 +140,6 @@ module ListingId =
|
||||
let value = function ListingId guid -> guid
|
||||
|
||||
|
||||
/// 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
|
||||
|
||||
/// Functions to support contact types
|
||||
module ContactType =
|
||||
|
||||
/// Parse a contact type from a string
|
||||
let parse typ =
|
||||
match typ with
|
||||
| "Email" -> Email
|
||||
| "Phone" -> Phone
|
||||
| "Website" -> Website
|
||||
| it -> invalidOp $"{it} is not a valid contact type"
|
||||
|
||||
/// Convert a contact type to its string representation
|
||||
let toString =
|
||||
function
|
||||
| Email -> "Email"
|
||||
| Phone -> "Phone"
|
||||
| Website -> "Website"
|
||||
|
||||
|
||||
/// Another way to contact a citizen from this site
|
||||
[<NoComparison; NoEquality>]
|
||||
type OtherContact =
|
||||
@@ -157,6 +157,34 @@ type OtherContact =
|
||||
}
|
||||
|
||||
|
||||
/// Visibility options for an employment profile
|
||||
type ProfileVisibility =
|
||||
/// Profile is only visible to authenticated users
|
||||
| Private
|
||||
/// Anonymous information is visible to public users
|
||||
| Anonymous
|
||||
/// The full employment profile is visible to public users
|
||||
| Public
|
||||
|
||||
/// Support functions for profile visibility
|
||||
module ProfileVisibility =
|
||||
|
||||
/// Parse a string into a profile visibility
|
||||
let parse viz =
|
||||
match viz with
|
||||
| "Private" -> Private
|
||||
| "Anonymous" -> Anonymous
|
||||
| "Public" -> Public
|
||||
| it -> invalidOp $"{it} is not a valid profile visibility value"
|
||||
|
||||
/// Convert a profile visibility to its string representation
|
||||
let toString =
|
||||
function
|
||||
| Private -> "Private"
|
||||
| Anonymous -> "Anonymous"
|
||||
| Public -> "Public"
|
||||
|
||||
|
||||
/// A skill the job seeker possesses
|
||||
[<NoComparison; NoEquality>]
|
||||
type Skill =
|
||||
@@ -370,25 +398,19 @@ type Profile =
|
||||
{ /// The ID of the citizen to whom this profile belongs
|
||||
Id : CitizenId
|
||||
|
||||
/// Whether this citizen is actively seeking employment
|
||||
IsSeekingEmployment : bool
|
||||
|
||||
/// Whether this citizen allows their profile to be a part of the publicly-viewable, anonymous data
|
||||
IsPubliclySearchable : bool
|
||||
|
||||
/// Whether this citizen allows their profile to be viewed via a public link
|
||||
IsPubliclyLinkable : bool
|
||||
|
||||
/// The ID of the continent on which the citizen resides
|
||||
ContinentId : ContinentId
|
||||
|
||||
/// The region in which the citizen resides
|
||||
Region : string
|
||||
|
||||
/// Whether the citizen is looking for remote work
|
||||
/// Whether this citizen is actively seeking employment
|
||||
IsSeekingEmployment : bool
|
||||
|
||||
/// Whether the citizen is interested in remote work
|
||||
IsRemote : bool
|
||||
|
||||
/// Whether the citizen is looking for full-time work
|
||||
/// Whether the citizen is interested in full-time work
|
||||
IsFullTime : bool
|
||||
|
||||
/// The citizen's professional biography
|
||||
@@ -403,6 +425,9 @@ type Profile =
|
||||
/// The citizen's experience (topical / chronological)
|
||||
Experience : MarkdownString option
|
||||
|
||||
/// The visibility of this profile
|
||||
Visibility : ProfileVisibility
|
||||
|
||||
/// When the citizen last updated their profile
|
||||
LastUpdatedOn : Instant
|
||||
|
||||
@@ -415,20 +440,19 @@ module Profile =
|
||||
|
||||
// An empty profile
|
||||
let empty = {
|
||||
Id = CitizenId Guid.Empty
|
||||
IsSeekingEmployment = false
|
||||
IsPubliclySearchable = false
|
||||
IsPubliclyLinkable = false
|
||||
ContinentId = ContinentId Guid.Empty
|
||||
Region = ""
|
||||
IsRemote = false
|
||||
IsFullTime = false
|
||||
Biography = Text ""
|
||||
Skills = []
|
||||
History = []
|
||||
Experience = None
|
||||
LastUpdatedOn = Instant.MinValue
|
||||
IsLegacy = false
|
||||
Id = CitizenId Guid.Empty
|
||||
ContinentId = ContinentId Guid.Empty
|
||||
Region = ""
|
||||
IsSeekingEmployment = false
|
||||
IsRemote = false
|
||||
IsFullTime = false
|
||||
Biography = Text ""
|
||||
Skills = []
|
||||
History = []
|
||||
Experience = None
|
||||
Visibility = Private
|
||||
LastUpdatedOn = Instant.MinValue
|
||||
IsLegacy = false
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -20,12 +20,12 @@ module Error =
|
||||
open System.Net
|
||||
|
||||
/// Handler that will return a status code 404 and the text "Not Found"
|
||||
let notFound : HttpHandler = fun next ctx ->
|
||||
let notFound : HttpHandler = fun _ ctx ->
|
||||
let fac = ctx.GetService<ILoggerFactory> ()
|
||||
let log = fac.CreateLogger "Handler"
|
||||
let path = string ctx.Request.Path
|
||||
log.LogInformation "Returning 404"
|
||||
RequestErrors.NOT_FOUND $"The URL {path} was not recognized as a valid URL" next ctx
|
||||
RequestErrors.NOT_FOUND $"The URL {path} was not recognized as a valid URL" earlyReturn ctx
|
||||
|
||||
|
||||
/// Handle unauthorized actions, redirecting to log on for GETs, otherwise returning a 401 Not Authorized response
|
||||
@@ -78,19 +78,10 @@ let tryUser (ctx : HttpContext) =
|
||||
|> Option.ofObj
|
||||
|> Option.map (fun x -> x.Value)
|
||||
|
||||
/// Require a user to be logged in
|
||||
let authorize : HttpHandler =
|
||||
fun next ctx -> match tryUser ctx with Some _ -> next ctx | None -> Error.notAuthorized next ctx
|
||||
|
||||
/// Get the ID of the currently logged in citizen
|
||||
// NOTE: if no one is logged in, this will raise an exception
|
||||
let currentCitizenId ctx = (tryUser >> Option.get >> CitizenId.ofString) ctx
|
||||
|
||||
/// Return an empty OK response
|
||||
let ok : HttpHandler = Successful.OK ""
|
||||
|
||||
// -- NEW --
|
||||
|
||||
let antiForgerySvc (ctx : HttpContext) =
|
||||
ctx.RequestServices.GetRequiredService<IAntiforgery> ()
|
||||
|
||||
@@ -168,6 +159,16 @@ let render pageTitle (_ : HttpFunc) (ctx : HttpContext) content = task {
|
||||
return! ctx.WriteHtmlViewAsync (renderFunc renderCtx)
|
||||
}
|
||||
|
||||
let renderBare (_ : HttpFunc) (ctx : HttpContext) content =
|
||||
({ IsLoggedOn = Option.isSome (tryUser ctx)
|
||||
CurrentUrl = ctx.Request.Path.Value
|
||||
PageTitle = ""
|
||||
Content = content
|
||||
Messages = []
|
||||
} : Layout.PageRenderContext)
|
||||
|> Layout.bare
|
||||
|> ctx.WriteHtmlViewAsync
|
||||
|
||||
/// Render as a composable HttpHandler
|
||||
let renderHandler pageTitle content : HttpHandler = fun next ctx ->
|
||||
render pageTitle next ctx content
|
||||
@@ -194,3 +195,7 @@ let redirectToGet (url : string) next ctx = task {
|
||||
else RequestErrors.BAD_REQUEST "Invalid redirect URL"
|
||||
return! action next ctx
|
||||
}
|
||||
|
||||
/// Shorthand for Error.notFound for use in handler functions
|
||||
let notFound ctx =
|
||||
Error.notFound earlyReturn ctx
|
||||
|
||||
@@ -19,13 +19,14 @@ open NodaTime.Serialization.SystemTextJson
|
||||
/// JsonSerializer options that use the custom converters
|
||||
let options =
|
||||
let opts = JsonSerializerOptions ()
|
||||
[ WrappedJsonConverter (CitizenId.ofString, CitizenId.toString) :> JsonConverter
|
||||
WrappedJsonConverter (ContactType.parse, ContactType.toString)
|
||||
WrappedJsonConverter (ContinentId.ofString, ContinentId.toString)
|
||||
WrappedJsonConverter (ListingId.ofString, ListingId.toString)
|
||||
WrappedJsonConverter (Text, MarkdownString.toString)
|
||||
WrappedJsonConverter (SuccessId.ofString, SuccessId.toString)
|
||||
JsonFSharpConverter ()
|
||||
[ WrappedJsonConverter (CitizenId.ofString, CitizenId.toString) :> JsonConverter
|
||||
WrappedJsonConverter (ContactType.parse, ContactType.toString)
|
||||
WrappedJsonConverter (ContinentId.ofString, ContinentId.toString)
|
||||
WrappedJsonConverter (ListingId.ofString, ListingId.toString)
|
||||
WrappedJsonConverter (Text, MarkdownString.toString)
|
||||
WrappedJsonConverter (ProfileVisibility.parse, ProfileVisibility.toString)
|
||||
WrappedJsonConverter (SuccessId.ofString, SuccessId.toString)
|
||||
JsonFSharpConverter ()
|
||||
]
|
||||
|> List.iter opts.Converters.Add
|
||||
let _ = opts.ConfigureForNodaTime DateTimeZoneProviders.Tzdb
|
||||
|
||||
@@ -352,3 +352,10 @@ module Layout =
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
/// Render a bare view (used for components)
|
||||
let bare ctx =
|
||||
html [ _lang "en" ] [
|
||||
head [] [ title [] [] ]
|
||||
body [] [ ctx.Content ]
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user