273 lines
7.3 KiB
Forth

module JobsJobsJobs.Api.Domain
// fsharplint:disable RecordFieldNames MemberNames
/// A short ID (12 characters of a Nano ID)
type ShortId =
| ShortId of string
/// Functions to maniuplate short IDs
module ShortId =
open Nanoid
open System.Text.RegularExpressions
/// Regular expression to validate a string's format as a short ID
let validShortId = Regex ("^[a-z0-9_-]{12}", RegexOptions.Compiled ||| RegexOptions.IgnoreCase)
/// Convert a short ID to its string representation
let toString = function ShortId text -> text
/// Create a new short ID
let create () = async {
let! text = Nanoid.GenerateAsync (size = 12)
return ShortId text
}
/// Try to parse a string into a short ID
let tryParse (text : string) =
match text.Length with
| 12 when validShortId.IsMatch text -> (ShortId >> Ok) text
| 12 -> Error "ShortId must be 12 characters [a-z,0-9,-, or _]"
| x -> Error (sprintf "ShortId must be 12 characters; %d provided" x)
/// The ID for a citizen (user) record
type CitizenId =
| CitizenId of ShortId
/// Functions for manipulating citizen (user) IDs
module CitizenId =
/// Convert a citizen ID to its string representation
let toString = function CitizenId shortId -> ShortId.toString shortId
/// Create a new citizen ID
let create () = async {
let! shortId = ShortId.create ()
return CitizenId shortId
}
/// Try to parse a string into a CitizenId
let tryParse text =
match ShortId.tryParse text with
| Ok shortId -> (CitizenId >> Ok) shortId
| Error err -> Error err
/// The ID for a continent record
type ContinentId =
| ContinentId of ShortId
/// Functions for manipulating continent IDs
module ContinentId =
/// Convert a continent ID to its string representation
let toString = function ContinentId shortId -> ShortId.toString shortId
/// Create a new continent ID
let create () = async {
let! shortId = ShortId.create ()
return ContinentId shortId
}
/// Try to parse a string into a ContinentId
let tryParse text =
match ShortId.tryParse text with
| Ok shortId -> (ContinentId >> Ok) shortId
| Error err -> Error err
/// The ID for a skill record
type SkillId =
| SkillId of ShortId
/// Functions for manipulating skill IDs
module SkillId =
/// Convert a skill ID to its string representation
let toString = function SkillId shortId -> ShortId.toString shortId
/// Create a new skill ID
let create () = async {
let! shortId = ShortId.create ()
return SkillId shortId
}
/// Try to parse a string into a CitizenId
let tryParse text =
match ShortId.tryParse text with
| Ok shortId -> (SkillId >> Ok) shortId
| Error err -> Error err
/// The ID for a success report record
type SuccessId =
| SuccessId of ShortId
/// Functions for manipulating success report IDs
module SuccessId =
/// Convert a success report ID to its string representation
let toString = function SuccessId shortId -> ShortId.toString shortId
/// Create a new success report ID
let create () = async {
let! shortId = ShortId.create ()
return SuccessId shortId
}
/// Try to parse a string into a SuccessId
let tryParse text =
match ShortId.tryParse text with
| Ok shortId -> (SuccessId >> Ok) shortId
| Error err -> Error err
/// A number representing milliseconds since the epoch (AKA JavaScript time)
type Millis =
| Millis of int64
/// Functions to manipulate ticks
module Millis =
/// Convert a Ticks instance to its primitive value
let toLong = function Millis millis -> millis
/// A string that holds Markdown-formatted text
type MarkdownString =
| MarkdownString of string
/// Functions to manipulate Markdown-formatted text
module MarkdownString =
open Markdig
/// Markdown pipeline that supports all built-in Markdown extensions
let private pipeline = MarkdownPipelineBuilder().UseAdvancedExtensions().Build ()
/// Get the plain-text (non-rendered) representation of the text
let toText = function MarkdownString str -> str
/// Get the HTML (rendered) representation of the text
let toHtml = function MarkdownString str -> Markdown.ToHtml (str, pipeline)
/// A user
type Citizen = {
/// The ID of the user
id : CitizenId
/// The user's handle on No Agenda Social
naUser : string
/// The user's display name from No Agenda Social (as of their last login here)
displayName : string
/// The URL to the user's profile on No Agenda Social
profileUrl : string
/// When the user signed up here
joinedOn : Millis
/// When the user last logged on here
lastSeenOn : Millis
}
/// A continent
type Continent = {
/// The ID of the continent
id : ContinentId
/// The name of the continent
name : string
}
/// An employment / skills profile
type Profile = {
/// The ID of the user to whom the profile applies
citizenId : CitizenId
/// Whether this user is actively seeking employment
seekingEmployment : bool
/// Whether information from this profile should appear in the public anonymous list of available skills
isPublic : bool
/// The continent on which the user is seeking employment
continentId : Continent
/// The region within that continent where the user would prefer to work
region : string
/// Whether the user is looking for remote work
remoteWork : bool
/// Whether the user is looking for full-time work
fullTime : bool
/// The user's professional biography
biography : MarkdownString
/// When this profile was last updated
lastUpdatedOn : Millis
/// The user's experience
experience : MarkdownString option
}
/// A skill which a user possesses
type Skill = {
/// The ID of the skill
id : SkillId
/// The ID of the user who possesses this skill
citizenId : CitizenId
/// The skill
skill : string
/// Notes about the skill (proficiency, experience, etc.)
notes : string option
}
/// A success story
type Success = {
/// The ID of the success story
id : SuccessId
/// The ID of the user who experienced this success story
citizenId : CitizenId
/// When this story was recorded
recordedOn : Millis
/// Whether the success came from here; if Jobs, Jobs, Jobs led them to eventual employment
fromHere : bool
/// Their success story
story : MarkdownString option
}
/// Configuration required for authentication with No Agenda Social
type AuthConfig = {
/// The client ID
clientId : string
/// The cryptographic secret
secret : string
/// The base URL for Mastodon's API access
apiUrl : string
}
/// Application configuration format
type JobsJobsJobsConfig = {
/// Auth0 configuration
auth : AuthConfig
/// Database connection URI
dbUri : string
}
open Microsoft.Extensions.Configuration
open System.IO
/// Configuration instance
let config =
(lazy
(let root =
ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory ())
.AddJsonFile("appsettings.json")
.AddJsonFile("appsettings.Development.json", true)
.AddJsonFile("appsettings.Production.json", true)
.AddEnvironmentVariables("JJJ_")
.Build()
let auth = root.GetSection "Auth"
{ dbUri = root.["dbUri"]
auth = {
clientId = auth.["ClientId"]
secret = auth.["Secret"]
apiUrl = auth.["ApiUrl"]
}
})).Force()