Add domain and API F# projects
This commit is contained in:
parent
d1c41d0329
commit
6e31f83c82
|
@ -20,6 +20,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||||
database\tables.sql = database\tables.sql
|
database\tables.sql = database\tables.sql
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "JobsJobsJobs", "JobsJobsJobs", "{FA833B24-B8F6-4CE6-A044-99257EAC02FF}"
|
||||||
|
EndProject
|
||||||
|
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Domain", "JobsJobsJobs\Domain\Domain.fsproj", "{C81278DA-DA97-4E55-AB39-4B88565B615D}"
|
||||||
|
EndProject
|
||||||
|
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Api", "JobsJobsJobs\Api\Api.fsproj", "{8F5A3D1E-562B-4F27-9787-6CB14B35E69E}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
@ -38,6 +44,14 @@ Global
|
||||||
{AE329284-47DA-4E76-B542-47489B271130}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{AE329284-47DA-4E76-B542-47489B271130}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{AE329284-47DA-4E76-B542-47489B271130}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{AE329284-47DA-4E76-B542-47489B271130}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{AE329284-47DA-4E76-B542-47489B271130}.Release|Any CPU.Build.0 = Release|Any CPU
|
{AE329284-47DA-4E76-B542-47489B271130}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{C81278DA-DA97-4E55-AB39-4B88565B615D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C81278DA-DA97-4E55-AB39-4B88565B615D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C81278DA-DA97-4E55-AB39-4B88565B615D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{C81278DA-DA97-4E55-AB39-4B88565B615D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{8F5A3D1E-562B-4F27-9787-6CB14B35E69E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{8F5A3D1E-562B-4F27-9787-6CB14B35E69E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{8F5A3D1E-562B-4F27-9787-6CB14B35E69E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{8F5A3D1E-562B-4F27-9787-6CB14B35E69E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
@ -45,4 +59,8 @@ Global
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {5E9ECDBF-634E-43A9-8F89-625A2213831C}
|
SolutionGuid = {5E9ECDBF-634E-43A9-8F89-625A2213831C}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{C81278DA-DA97-4E55-AB39-4B88565B615D} = {FA833B24-B8F6-4CE6-A044-99257EAC02FF}
|
||||||
|
{8F5A3D1E-562B-4F27-9787-6CB14B35E69E} = {FA833B24-B8F6-4CE6-A044-99257EAC02FF}
|
||||||
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
25
src/JobsJobsJobs/Api/Api.fsproj
Normal file
25
src/JobsJobsJobs/Api/Api.fsproj
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
<WarnOn>3390;$(WarnOn)</WarnOn>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Data.fs" />
|
||||||
|
<Compile Include="App.fs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Domain\Domain.fsproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Giraffe" Version="5.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.FSharpLu.Json" Version="0.11.7" />
|
||||||
|
<PackageReference Include="Polly" Version="7.2.2" />
|
||||||
|
<PackageReference Include="RethinkDb.Driver" Version="2.3.150" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
51
src/JobsJobsJobs/Api/App.fs
Normal file
51
src/JobsJobsJobs/Api/App.fs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
/// The main API application for Jobs, Jobs, Jobs
|
||||||
|
module JobsJobsJobs.Api.App
|
||||||
|
|
||||||
|
//open System
|
||||||
|
open Microsoft.AspNetCore.Builder
|
||||||
|
open Microsoft.AspNetCore.Hosting
|
||||||
|
open Microsoft.Extensions.DependencyInjection
|
||||||
|
open Microsoft.Extensions.Hosting
|
||||||
|
open Giraffe
|
||||||
|
|
||||||
|
/// All available routes for the application
|
||||||
|
let webApp =
|
||||||
|
choose [
|
||||||
|
route "/ping" >=> text "pong"
|
||||||
|
route "/" >=> htmlFile "/pages/index.html"
|
||||||
|
]
|
||||||
|
|
||||||
|
/// Configure the ASP.NET Core pipeline to use Giraffe
|
||||||
|
let configureApp (app : IApplicationBuilder) =
|
||||||
|
app.UseGiraffe webApp
|
||||||
|
|
||||||
|
open NodaTime
|
||||||
|
open RethinkDb.Driver.Net
|
||||||
|
open Microsoft.Extensions.Configuration
|
||||||
|
open Microsoft.Extensions.Logging
|
||||||
|
|
||||||
|
/// Configure dependency injection
|
||||||
|
let configureServices (svc : IServiceCollection) =
|
||||||
|
svc.AddGiraffe()
|
||||||
|
.AddSingleton<IClock>(SystemClock.Instance)
|
||||||
|
.AddLogging ()
|
||||||
|
|> ignore
|
||||||
|
let svcs = svc.BuildServiceProvider()
|
||||||
|
let cfg = svcs.GetRequiredService<IConfiguration>().GetSection "Rethink"
|
||||||
|
let log = svcs.GetRequiredService<ILoggerFactory>().CreateLogger "Data.Startup"
|
||||||
|
let conn = Data.Startup.createConnection cfg log
|
||||||
|
svc.AddSingleton conn |> ignore
|
||||||
|
Data.Startup.establishEnvironment cfg log conn |> Data.awaitIgnore
|
||||||
|
|
||||||
|
[<EntryPoint>]
|
||||||
|
let main _ =
|
||||||
|
Host.CreateDefaultBuilder()
|
||||||
|
.ConfigureWebHostDefaults(
|
||||||
|
fun webHostBuilder ->
|
||||||
|
webHostBuilder
|
||||||
|
.Configure(configureApp)
|
||||||
|
.ConfigureServices(configureServices)
|
||||||
|
|> ignore)
|
||||||
|
.Build()
|
||||||
|
.Run ()
|
||||||
|
0
|
169
src/JobsJobsJobs/Api/Data.fs
Normal file
169
src/JobsJobsJobs/Api/Data.fs
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
/// Data access functions for Jobs, Jobs, Jobs
|
||||||
|
module JobsJobsJobs.Api.Data
|
||||||
|
|
||||||
|
open JobsJobsJobs.Domain
|
||||||
|
open JobsJobsJobs.Domain.Types
|
||||||
|
open Polly
|
||||||
|
open RethinkDb.Driver
|
||||||
|
open RethinkDb.Driver.Net
|
||||||
|
open Microsoft.Extensions.Configuration
|
||||||
|
open FSharp.Control.Tasks
|
||||||
|
open Microsoft.Extensions.Logging
|
||||||
|
|
||||||
|
/// Shorthand for the RethinkDB R variable (how every command starts)
|
||||||
|
let private r = RethinkDB.R
|
||||||
|
|
||||||
|
/// Shorthand for await task / run sync / ignore (used in non-async contexts)
|
||||||
|
let awaitIgnore x = x |> Async.AwaitTask |> Async.RunSynchronously |> ignore
|
||||||
|
|
||||||
|
|
||||||
|
/// JSON converters used with RethinkDB persistence
|
||||||
|
module Converters =
|
||||||
|
|
||||||
|
open Microsoft.FSharpLu.Json
|
||||||
|
open Newtonsoft.Json
|
||||||
|
open System
|
||||||
|
|
||||||
|
/// JSON converter for citizen IDs
|
||||||
|
type CitizenIdJsonConverter() =
|
||||||
|
inherit JsonConverter<CitizenId>()
|
||||||
|
override __.WriteJson(writer : JsonWriter, value : CitizenId, _ : JsonSerializer) =
|
||||||
|
writer.WriteValue (CitizenId.toString value)
|
||||||
|
override __.ReadJson(reader: JsonReader, _ : Type, _ : CitizenId, _ : bool, _ : JsonSerializer) =
|
||||||
|
(string >> CitizenId.ofString) reader.Value
|
||||||
|
|
||||||
|
/// JSON converter for continent IDs
|
||||||
|
type ContinentIdJsonConverter() =
|
||||||
|
inherit JsonConverter<ContinentId>()
|
||||||
|
override __.WriteJson(writer : JsonWriter, value : ContinentId, _ : JsonSerializer) =
|
||||||
|
writer.WriteValue (ContinentId.toString value)
|
||||||
|
override __.ReadJson(reader: JsonReader, _ : Type, _ : ContinentId, _ : bool, _ : JsonSerializer) =
|
||||||
|
(string >> ContinentId.ofString) reader.Value
|
||||||
|
|
||||||
|
/// JSON converter for Markdown strings
|
||||||
|
type MarkdownStringJsonConverter() =
|
||||||
|
inherit JsonConverter<MarkdownString>()
|
||||||
|
override __.WriteJson(writer : JsonWriter, value : MarkdownString, _ : JsonSerializer) =
|
||||||
|
let (Text text) = value
|
||||||
|
writer.WriteValue text
|
||||||
|
override __.ReadJson(reader: JsonReader, _ : Type, _ : MarkdownString, _ : bool, _ : JsonSerializer) =
|
||||||
|
(string >> Text) reader.Value
|
||||||
|
|
||||||
|
/// JSON converter for listing IDs
|
||||||
|
type ListingIdJsonConverter() =
|
||||||
|
inherit JsonConverter<ListingId>()
|
||||||
|
override __.WriteJson(writer : JsonWriter, value : ListingId, _ : JsonSerializer) =
|
||||||
|
writer.WriteValue (ListingId.toString value)
|
||||||
|
override __.ReadJson(reader: JsonReader, _ : Type, _ : ListingId, _ : bool, _ : JsonSerializer) =
|
||||||
|
(string >> ListingId.ofString) reader.Value
|
||||||
|
|
||||||
|
/// JSON converter for skill IDs
|
||||||
|
type SkillIdJsonConverter() =
|
||||||
|
inherit JsonConverter<SkillId>()
|
||||||
|
override __.WriteJson(writer : JsonWriter, value : SkillId, _ : JsonSerializer) =
|
||||||
|
writer.WriteValue (SkillId.toString value)
|
||||||
|
override __.ReadJson(reader: JsonReader, _ : Type, _ : SkillId, _ : bool, _ : JsonSerializer) =
|
||||||
|
(string >> SkillId.ofString) reader.Value
|
||||||
|
|
||||||
|
/// JSON converter for success report IDs
|
||||||
|
type SuccessIdJsonConverter() =
|
||||||
|
inherit JsonConverter<SuccessId>()
|
||||||
|
override __.WriteJson(writer : JsonWriter, value : SuccessId, _ : JsonSerializer) =
|
||||||
|
writer.WriteValue (SuccessId.toString value)
|
||||||
|
override __.ReadJson(reader: JsonReader, _ : Type, _ : SuccessId, _ : bool, _ : JsonSerializer) =
|
||||||
|
(string >> SuccessId.ofString) reader.Value
|
||||||
|
|
||||||
|
/// All JSON converters needed for the application
|
||||||
|
let all () = [
|
||||||
|
CitizenIdJsonConverter () :> JsonConverter
|
||||||
|
upcast ContinentIdJsonConverter ()
|
||||||
|
upcast MarkdownStringJsonConverter ()
|
||||||
|
upcast ListingIdJsonConverter ()
|
||||||
|
upcast SkillIdJsonConverter ()
|
||||||
|
upcast SuccessIdJsonConverter ()
|
||||||
|
upcast CompactUnionJsonConverter ()
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
/// Table names
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Table =
|
||||||
|
/// The user (citizen of Gitmo Nation) table
|
||||||
|
let Citizen = "citizen"
|
||||||
|
/// The continent table
|
||||||
|
let Continent = "continent"
|
||||||
|
/// The job listing table
|
||||||
|
let Listing = "listing"
|
||||||
|
/// The citizen employment profile table
|
||||||
|
let Profile = "profile"
|
||||||
|
/// The success story table
|
||||||
|
let Success = "success"
|
||||||
|
/// All tables
|
||||||
|
let all () = [ Citizen; Continent; Listing; Profile; Success ]
|
||||||
|
|
||||||
|
|
||||||
|
/// Functions run at startup
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Startup =
|
||||||
|
|
||||||
|
/// Create a RethinkDB connection
|
||||||
|
let createConnection (cfg : IConfigurationSection) (log : ILogger)=
|
||||||
|
|
||||||
|
// Add all required JSON converters
|
||||||
|
Converters.all ()
|
||||||
|
|> List.iter Converter.Serializer.Converters.Add
|
||||||
|
// Read the configuration and create a connection
|
||||||
|
let bldr =
|
||||||
|
seq<Connection.Builder -> Connection.Builder> {
|
||||||
|
yield fun b -> match cfg.["Hostname"] with null -> b | host -> b.Hostname host
|
||||||
|
yield fun b -> match cfg.["Port"] with null -> b | port -> (int >> b.Port) port
|
||||||
|
yield fun b -> match cfg.["AuthKey"] with null -> b | key -> b.AuthKey key
|
||||||
|
yield fun b -> match cfg.["Db"] with null -> b | db -> b.Db db
|
||||||
|
yield fun b -> match cfg.["Timeout"] with null -> b | time -> (int >> b.Timeout) time
|
||||||
|
}
|
||||||
|
|> Seq.fold (fun b step -> step b) (r.Connection ())
|
||||||
|
match log.IsEnabled LogLevel.Debug with
|
||||||
|
| true -> log.LogDebug $"RethinkDB: Connecting to {bldr.Hostname}:{bldr.Port}, database {bldr.Db}"
|
||||||
|
| false -> ()
|
||||||
|
bldr.Connect () :> IConnection
|
||||||
|
|
||||||
|
/// Ensure the data, tables, and indexes that are required exist
|
||||||
|
let establishEnvironment (cfg : IConfigurationSection) (log : ILogger) conn = task {
|
||||||
|
// Ensure the database exists
|
||||||
|
match cfg.["Db"] |> Option.ofObj with
|
||||||
|
| Some database ->
|
||||||
|
let! dbs = r.DbList().RunResultAsync<string list> conn
|
||||||
|
match dbs |> List.contains database with
|
||||||
|
| true -> ()
|
||||||
|
| false ->
|
||||||
|
log.LogInformation $"Creating database {database}..."
|
||||||
|
let! _ = r.DbCreate(database).RunWriteAsync conn
|
||||||
|
()
|
||||||
|
| None -> ()
|
||||||
|
// Ensure the tables exist
|
||||||
|
let! tables = r.TableList().RunResultAsync<string list> conn
|
||||||
|
Table.all ()
|
||||||
|
|> List.iter (
|
||||||
|
fun tbl ->
|
||||||
|
match tables |> List.contains tbl with
|
||||||
|
| true -> ()
|
||||||
|
| false ->
|
||||||
|
log.LogInformation $"Creating {tbl} table..."
|
||||||
|
r.TableCreate(tbl).RunWriteAsync conn |> awaitIgnore)
|
||||||
|
// Ensure the indexes exist
|
||||||
|
let ensureIndexes table indexes = task {
|
||||||
|
let! tblIdxs = r.Table(table).IndexList().RunResultAsync<string list> conn
|
||||||
|
indexes
|
||||||
|
|> List.iter (
|
||||||
|
fun idx ->
|
||||||
|
match tblIdxs |> List.contains idx with
|
||||||
|
| true -> ()
|
||||||
|
| false ->
|
||||||
|
log.LogInformation $"Creating \"{idx}\" index on {table}"
|
||||||
|
r.Table(table).IndexCreate(idx).RunWriteAsync conn |> awaitIgnore)
|
||||||
|
}
|
||||||
|
do! ensureIndexes Table.Citizen [ "naUser" ]
|
||||||
|
do! ensureIndexes Table.Listing [ "citizenId"; "continentId" ]
|
||||||
|
do! ensureIndexes Table.Profile [ "continentId" ]
|
||||||
|
do! ensureIndexes Table.Success [ "citizenId" ]
|
||||||
|
}
|
6
src/JobsJobsJobs/Api/appsettings.json
Normal file
6
src/JobsJobsJobs/Api/appsettings.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"Rethink": {
|
||||||
|
"Hostname": "localhost",
|
||||||
|
"Db": "jobsjobsjobs"
|
||||||
|
}
|
||||||
|
}
|
19
src/JobsJobsJobs/Domain/Domain.fsproj
Normal file
19
src/JobsJobsJobs/Domain/Domain.fsproj
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<WarnOn>3390;$(WarnOn)</WarnOn>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Types.fs" />
|
||||||
|
<Compile Include="Modules.fs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Markdig" Version="0.25.0" />
|
||||||
|
<PackageReference Include="NodaTime" Version="3.0.5" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
86
src/JobsJobsJobs/Domain/Modules.fs
Normal file
86
src/JobsJobsJobs/Domain/Modules.fs
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
/// Modules to provide support functions for types
|
||||||
|
[<AutoOpen>]
|
||||||
|
module JobsJobsJobs.Domain.Modules
|
||||||
|
|
||||||
|
open Markdig
|
||||||
|
open System
|
||||||
|
open Types
|
||||||
|
|
||||||
|
/// Format a GUID as a Short GUID
|
||||||
|
let private toShortGuid guid =
|
||||||
|
let convert (g : Guid) =
|
||||||
|
Convert.ToBase64String (g.ToByteArray ())
|
||||||
|
|> String.map (fun x -> match x with '/' -> '_' | '+' -> '-' | _ -> x)
|
||||||
|
(convert guid).Substring (0, 22)
|
||||||
|
|
||||||
|
/// Turn a Short GUID back into a GUID
|
||||||
|
let private fromShortGuid x =
|
||||||
|
let unBase64 = x |> String.map (fun x -> match x with '_' -> '/' | '-' -> '+' | _ -> x)
|
||||||
|
(Convert.FromBase64String >> Guid) $"{unBase64}=="
|
||||||
|
|
||||||
|
|
||||||
|
/// Support functions for citizen IDs
|
||||||
|
module CitizenId =
|
||||||
|
/// Create a new citizen ID
|
||||||
|
let create () = (Guid.NewGuid >> CitizenId) ()
|
||||||
|
/// A string representation of a citizen ID
|
||||||
|
let toString = function (CitizenId it) -> toShortGuid it
|
||||||
|
/// Parse a string into a citizen ID
|
||||||
|
let ofString = fromShortGuid >> CitizenId
|
||||||
|
|
||||||
|
|
||||||
|
/// Support functions for citizens
|
||||||
|
module Citizen =
|
||||||
|
/// Get the name of the citizen (the first of real name, display name, or handle that is filled in)
|
||||||
|
let name x =
|
||||||
|
[ x.realName; x.displayName; Some x.naUser ]
|
||||||
|
|> List.find Option.isSome
|
||||||
|
|> Option.get
|
||||||
|
|
||||||
|
|
||||||
|
/// Support functions for continent IDs
|
||||||
|
module ContinentId =
|
||||||
|
/// Create a new continent ID
|
||||||
|
let create () = (Guid.NewGuid >> ContinentId) ()
|
||||||
|
/// A string representation of a continent ID
|
||||||
|
let toString = function (ContinentId it) -> toShortGuid it
|
||||||
|
/// Parse a string into a continent ID
|
||||||
|
let ofString = fromShortGuid >> ContinentId
|
||||||
|
|
||||||
|
|
||||||
|
/// Support functions for Markdown strings
|
||||||
|
module MarkdownString =
|
||||||
|
/// The Markdown conversion pipeline (enables all advanced features)
|
||||||
|
let private pipeline = MarkdownPipelineBuilder().UseAdvancedExtensions().Build ()
|
||||||
|
/// Convert this Markdown string to HTML
|
||||||
|
let toHtml = function (Text text) -> Markdown.ToHtml (text, pipeline)
|
||||||
|
|
||||||
|
|
||||||
|
/// Support functions for listing IDs
|
||||||
|
module ListingId =
|
||||||
|
/// Create a new job listing ID
|
||||||
|
let create () = (Guid.NewGuid >> ListingId) ()
|
||||||
|
/// A string representation of a listing ID
|
||||||
|
let toString = function (ListingId it) -> toShortGuid it
|
||||||
|
/// Parse a string into a listing ID
|
||||||
|
let ofString = fromShortGuid >> ListingId
|
||||||
|
|
||||||
|
|
||||||
|
/// Support functions for skill IDs
|
||||||
|
module SkillId =
|
||||||
|
/// Create a new skill ID
|
||||||
|
let create () = (Guid.NewGuid >> SkillId) ()
|
||||||
|
/// A string representation of a skill ID
|
||||||
|
let toString = function (SkillId it) -> toShortGuid it
|
||||||
|
/// Parse a string into a skill ID
|
||||||
|
let ofString = fromShortGuid >> SkillId
|
||||||
|
|
||||||
|
|
||||||
|
/// Support functions for success report IDs
|
||||||
|
module SuccessId =
|
||||||
|
/// Create a new success report ID
|
||||||
|
let create () = (Guid.NewGuid >> SuccessId) ()
|
||||||
|
/// A string representation of a success report ID
|
||||||
|
let toString = function (SuccessId it) -> toShortGuid it
|
||||||
|
/// Parse a string into a success report ID
|
||||||
|
let ofString = fromShortGuid >> SuccessId
|
136
src/JobsJobsJobs/Domain/Types.fs
Normal file
136
src/JobsJobsJobs/Domain/Types.fs
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
/// Types within Jobs, Jobs, Jobs
|
||||||
|
module JobsJobsJobs.Domain.Types
|
||||||
|
|
||||||
|
open NodaTime
|
||||||
|
open System
|
||||||
|
|
||||||
|
// fsharplint:disable FieldNames
|
||||||
|
|
||||||
|
/// The ID of a user (a citizen of Gitmo Nation)
|
||||||
|
type CitizenId = CitizenId of Guid
|
||||||
|
|
||||||
|
/// A user of Jobs, Jobs, Jobs
|
||||||
|
type Citizen = {
|
||||||
|
/// The ID of the user
|
||||||
|
id : CitizenId
|
||||||
|
/// The handle by which the user is known on Mastodon
|
||||||
|
naUser : string
|
||||||
|
/// The user's display name from Mastodon (updated every login)
|
||||||
|
displayName : string option
|
||||||
|
/// The user's real name
|
||||||
|
realName : string option
|
||||||
|
/// The URL for the user's Mastodon profile
|
||||||
|
profileUrl : string
|
||||||
|
/// When the user joined Jobs, Jobs, Jobs
|
||||||
|
joinedOn : Instant
|
||||||
|
/// When the user last logged in
|
||||||
|
lastSeenOn : Instant
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// The ID of a continent
|
||||||
|
type ContinentId = ContinentId of Guid
|
||||||
|
|
||||||
|
/// A continent
|
||||||
|
type Continent = {
|
||||||
|
/// The ID of the continent
|
||||||
|
id : ContinentId
|
||||||
|
/// The name of the continent
|
||||||
|
name : string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// A string of Markdown text
|
||||||
|
type MarkdownString = Text of string
|
||||||
|
|
||||||
|
|
||||||
|
/// The ID of a job listing
|
||||||
|
type ListingId = ListingId of Guid
|
||||||
|
|
||||||
|
/// A job listing
|
||||||
|
type Listing = {
|
||||||
|
/// The ID of the job listing
|
||||||
|
id : ListingId
|
||||||
|
/// The ID of the citizen who posted the job listing
|
||||||
|
citizenId : CitizenId
|
||||||
|
/// When this job listing was created
|
||||||
|
createdOn : Instant
|
||||||
|
/// The short title of the job listing
|
||||||
|
title : string
|
||||||
|
/// The ID of the continent on which the job is located
|
||||||
|
continentId : ContinentId
|
||||||
|
/// The region in which the job is located
|
||||||
|
region : string
|
||||||
|
/// Whether this listing is for remote work
|
||||||
|
remoteWork : bool
|
||||||
|
/// Whether this listing has expired
|
||||||
|
isExpired : bool
|
||||||
|
/// When this listing was last updated
|
||||||
|
updatedOn : Instant
|
||||||
|
/// The details of this job
|
||||||
|
text : MarkdownString
|
||||||
|
/// When this job needs to be filled
|
||||||
|
neededBy : LocalDate option
|
||||||
|
/// Was this job filled as part of its appearance on Jobs, Jobs, Jobs?
|
||||||
|
wasFilledHere : bool option
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// The ID of a skill
|
||||||
|
type SkillId = SkillId of Guid
|
||||||
|
|
||||||
|
/// A skill the job seeker possesses
|
||||||
|
type Skill = {
|
||||||
|
/// The ID of the skill
|
||||||
|
id : SkillId
|
||||||
|
/// A description of the skill
|
||||||
|
description : string
|
||||||
|
/// Notes regarding this skill (level, duration, etc.)
|
||||||
|
notes : string option
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// A job seeker profile
|
||||||
|
type Profile = {
|
||||||
|
/// The ID of the citizen to whom this profile belongs
|
||||||
|
id : CitizenId
|
||||||
|
/// Whether this citizen is actively seeking employment
|
||||||
|
seekingEmployment : bool
|
||||||
|
/// Whether this citizen allows their profile to be a part of the publicly-viewable, anonymous data
|
||||||
|
isPublic : 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
|
||||||
|
remoteWork : bool
|
||||||
|
/// Whether the citizen is looking for full-time work
|
||||||
|
fullTime : bool
|
||||||
|
/// The citizen's professional biography
|
||||||
|
biography : MarkdownString
|
||||||
|
/// When the citizen last updated their profile
|
||||||
|
lastUpdatedOn : Instant
|
||||||
|
/// The citizen's experience (topical / chronological)
|
||||||
|
experience : MarkdownString option
|
||||||
|
/// Skills this citizen possesses
|
||||||
|
skills : Skill list
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The ID of a success report
|
||||||
|
type SuccessId = SuccessId of Guid
|
||||||
|
|
||||||
|
/// A record of success finding employment
|
||||||
|
type Success = {
|
||||||
|
/// The ID of the success report
|
||||||
|
id : SuccessId
|
||||||
|
/// The ID of the citizen who wrote this success report
|
||||||
|
citizenId : CitizenId
|
||||||
|
/// When this success report was recorded
|
||||||
|
recordedOn : Instant
|
||||||
|
/// Whether the success was due, at least in part, to Jobs, Jobs, Jobs
|
||||||
|
fromHere : bool
|
||||||
|
/// The source of this success (listing or profile)
|
||||||
|
source : string
|
||||||
|
/// The success story
|
||||||
|
story : MarkdownString option
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user