Version 2.2.2 (#35)
- Allow instances to be disabled (partial fix for #29 and #33) - Use RethinkDB F# driver (partial fix for #34) - Add FAKE build script (addresses #30)
This commit is contained in:
parent
6e52688622
commit
323ea83594
12
.config/dotnet-tools.json
Normal file
12
.config/dotnet-tools.json
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"isRoot": true,
|
||||||
|
"tools": {
|
||||||
|
"fake-cli": {
|
||||||
|
"version": "5.22.0",
|
||||||
|
"commands": [
|
||||||
|
"fake"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -4,3 +4,4 @@ src/**/bin
|
||||||
src/**/obj
|
src/**/obj
|
||||||
src/**/appsettings.*.json
|
src/**/appsettings.*.json
|
||||||
src/.vs
|
src/.vs
|
||||||
|
src/.idea
|
||||||
|
|
78
build.fsx
Normal file
78
build.fsx
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
#r "paket:
|
||||||
|
nuget Fake.DotNet.Cli
|
||||||
|
nuget Fake.IO.FileSystem
|
||||||
|
nuget Fake.JavaScript.Npm
|
||||||
|
nuget Fake.Core.Target //"
|
||||||
|
#load ".fake/build.fsx/intellisense.fsx"
|
||||||
|
open Fake.Core
|
||||||
|
open Fake.DotNet
|
||||||
|
open Fake.IO
|
||||||
|
open Fake.IO.Globbing.Operators
|
||||||
|
open Fake.JavaScript
|
||||||
|
open Fake.Core.TargetOperators
|
||||||
|
|
||||||
|
/// The path to the Vue client
|
||||||
|
let clientPath = "src/JobsJobsJobs/App"
|
||||||
|
|
||||||
|
/// The path to the API server
|
||||||
|
let serverPath = "src/JobsJobsJobs/Server"
|
||||||
|
|
||||||
|
Target.initEnvironment ()
|
||||||
|
|
||||||
|
Target.create "Clean" (fun _ ->
|
||||||
|
!! "src/**/bin"
|
||||||
|
++ "src/**/obj"
|
||||||
|
++ $"{serverPath}/wwwroot"
|
||||||
|
|> Shell.cleanDirs
|
||||||
|
)
|
||||||
|
|
||||||
|
Target.create "BuildClient" (fun _ ->
|
||||||
|
let inClientPath (opts : Npm.NpmParams) = { opts with WorkingDirectory = clientPath; }
|
||||||
|
Npm.exec "i --legacy-peer-deps" inClientPath
|
||||||
|
Npm.run "build" inClientPath
|
||||||
|
)
|
||||||
|
|
||||||
|
Target.create "BuildServer" (fun _ ->
|
||||||
|
DotNet.build (fun opts -> { opts with NoLogo = true }) serverPath
|
||||||
|
)
|
||||||
|
|
||||||
|
Target.create "RunServer" (fun _ ->
|
||||||
|
DotNet.exec (fun opts -> { opts with WorkingDirectory = serverPath }) "run" "" |> ignore
|
||||||
|
)
|
||||||
|
|
||||||
|
Target.create "Publish" (fun _ ->
|
||||||
|
DotNet.publish
|
||||||
|
(fun opts -> { opts with Runtime = Some "linux-x64"; SelfContained = Some false; NoLogo = true })
|
||||||
|
serverPath
|
||||||
|
)
|
||||||
|
|
||||||
|
Target.create "BuildAndRun" ignore
|
||||||
|
Target.create "All" ignore
|
||||||
|
|
||||||
|
"Clean"
|
||||||
|
==> "All"
|
||||||
|
"Clean"
|
||||||
|
==> "Publish"
|
||||||
|
"Clean"
|
||||||
|
?=> "BuildClient"
|
||||||
|
"Clean"
|
||||||
|
==> "BuildAndRun"
|
||||||
|
|
||||||
|
"BuildClient"
|
||||||
|
==> "All"
|
||||||
|
"BuildClient"
|
||||||
|
?=> "BuildServer"
|
||||||
|
"BuildClient"
|
||||||
|
?=> "RunServer"
|
||||||
|
"BuildClient"
|
||||||
|
==> "BuildAndRun"
|
||||||
|
"BuildClient"
|
||||||
|
==> "Publish"
|
||||||
|
|
||||||
|
"BuildServer"
|
||||||
|
==> "All"
|
||||||
|
|
||||||
|
"RunServer"
|
||||||
|
==> "BuildAndRun"
|
||||||
|
|
||||||
|
Target.runOrDefault "All"
|
232
build.fsx.lock
Normal file
232
build.fsx.lock
Normal file
|
@ -0,0 +1,232 @@
|
||||||
|
STORAGE: NONE
|
||||||
|
RESTRICTION: || (== net6.0) (== netstandard2.0)
|
||||||
|
NUGET
|
||||||
|
remote: https://api.nuget.org/v3/index.json
|
||||||
|
BlackFox.VsWhere (1.1)
|
||||||
|
FSharp.Core (>= 4.2.3)
|
||||||
|
Microsoft.Win32.Registry (>= 4.7)
|
||||||
|
Fake.Core.CommandLineParsing (5.22)
|
||||||
|
FParsec (>= 1.1.1)
|
||||||
|
FSharp.Core (>= 6.0)
|
||||||
|
Fake.Core.Context (5.22)
|
||||||
|
FSharp.Core (>= 6.0)
|
||||||
|
Fake.Core.Environment (5.22)
|
||||||
|
FSharp.Core (>= 6.0)
|
||||||
|
Fake.Core.FakeVar (5.22)
|
||||||
|
Fake.Core.Context (>= 5.22)
|
||||||
|
FSharp.Core (>= 6.0)
|
||||||
|
Fake.Core.Process (5.22)
|
||||||
|
Fake.Core.Environment (>= 5.22)
|
||||||
|
Fake.Core.FakeVar (>= 5.22)
|
||||||
|
Fake.Core.String (>= 5.22)
|
||||||
|
Fake.Core.Trace (>= 5.22)
|
||||||
|
Fake.IO.FileSystem (>= 5.22)
|
||||||
|
FSharp.Core (>= 6.0)
|
||||||
|
System.Collections.Immutable (>= 5.0)
|
||||||
|
Fake.Core.SemVer (5.22)
|
||||||
|
FSharp.Core (>= 6.0)
|
||||||
|
Fake.Core.String (5.22)
|
||||||
|
FSharp.Core (>= 6.0)
|
||||||
|
Fake.Core.Target (5.22)
|
||||||
|
Fake.Core.CommandLineParsing (>= 5.22)
|
||||||
|
Fake.Core.Context (>= 5.22)
|
||||||
|
Fake.Core.Environment (>= 5.22)
|
||||||
|
Fake.Core.FakeVar (>= 5.22)
|
||||||
|
Fake.Core.Process (>= 5.22)
|
||||||
|
Fake.Core.String (>= 5.22)
|
||||||
|
Fake.Core.Trace (>= 5.22)
|
||||||
|
FSharp.Control.Reactive (>= 5.0.2)
|
||||||
|
FSharp.Core (>= 6.0)
|
||||||
|
Fake.Core.Tasks (5.22)
|
||||||
|
Fake.Core.Trace (>= 5.22)
|
||||||
|
FSharp.Core (>= 6.0)
|
||||||
|
Fake.Core.Trace (5.22)
|
||||||
|
Fake.Core.Environment (>= 5.22)
|
||||||
|
Fake.Core.FakeVar (>= 5.22)
|
||||||
|
FSharp.Core (>= 6.0)
|
||||||
|
Fake.Core.Xml (5.22)
|
||||||
|
Fake.Core.String (>= 5.22)
|
||||||
|
FSharp.Core (>= 6.0)
|
||||||
|
Fake.DotNet.Cli (5.22)
|
||||||
|
Fake.Core.Environment (>= 5.22)
|
||||||
|
Fake.Core.Process (>= 5.22)
|
||||||
|
Fake.Core.String (>= 5.22)
|
||||||
|
Fake.Core.Trace (>= 5.22)
|
||||||
|
Fake.DotNet.MSBuild (>= 5.22)
|
||||||
|
Fake.DotNet.NuGet (>= 5.22)
|
||||||
|
Fake.IO.FileSystem (>= 5.22)
|
||||||
|
FSharp.Core (>= 6.0)
|
||||||
|
Mono.Posix.NETStandard (>= 1.0)
|
||||||
|
Newtonsoft.Json (>= 13.0.1)
|
||||||
|
Fake.DotNet.MSBuild (5.22)
|
||||||
|
BlackFox.VsWhere (>= 1.1)
|
||||||
|
Fake.Core.Environment (>= 5.22)
|
||||||
|
Fake.Core.Process (>= 5.22)
|
||||||
|
Fake.Core.String (>= 5.22)
|
||||||
|
Fake.Core.Trace (>= 5.22)
|
||||||
|
Fake.IO.FileSystem (>= 5.22)
|
||||||
|
FSharp.Core (>= 6.0)
|
||||||
|
MSBuild.StructuredLogger (>= 2.1.545)
|
||||||
|
Fake.DotNet.NuGet (5.22)
|
||||||
|
Fake.Core.Environment (>= 5.22)
|
||||||
|
Fake.Core.Process (>= 5.22)
|
||||||
|
Fake.Core.SemVer (>= 5.22)
|
||||||
|
Fake.Core.String (>= 5.22)
|
||||||
|
Fake.Core.Tasks (>= 5.22)
|
||||||
|
Fake.Core.Trace (>= 5.22)
|
||||||
|
Fake.Core.Xml (>= 5.22)
|
||||||
|
Fake.IO.FileSystem (>= 5.22)
|
||||||
|
Fake.Net.Http (>= 5.22)
|
||||||
|
FSharp.Core (>= 6.0)
|
||||||
|
Newtonsoft.Json (>= 13.0.1)
|
||||||
|
NuGet.Protocol (>= 5.11)
|
||||||
|
Fake.IO.FileSystem (5.22)
|
||||||
|
Fake.Core.String (>= 5.22)
|
||||||
|
FSharp.Core (>= 6.0)
|
||||||
|
Fake.JavaScript.Npm (5.22)
|
||||||
|
Fake.Core.Environment (>= 5.22)
|
||||||
|
Fake.Core.Process (>= 5.22)
|
||||||
|
Fake.IO.FileSystem (>= 5.22)
|
||||||
|
Fake.Testing.Common (>= 5.22)
|
||||||
|
FSharp.Core (>= 6.0)
|
||||||
|
Fake.Net.Http (5.22)
|
||||||
|
Fake.Core.Trace (>= 5.22)
|
||||||
|
FSharp.Core (>= 6.0)
|
||||||
|
Fake.Testing.Common (5.22)
|
||||||
|
Fake.Core.Trace (>= 5.22)
|
||||||
|
FSharp.Core (>= 6.0)
|
||||||
|
FParsec (1.1.1)
|
||||||
|
FSharp.Core (>= 4.3.4)
|
||||||
|
FSharp.Control.Reactive (5.0.5)
|
||||||
|
FSharp.Core (>= 4.7.2)
|
||||||
|
System.Reactive (>= 5.0 < 6.0)
|
||||||
|
FSharp.Core (6.0.5)
|
||||||
|
Microsoft.Build (17.2)
|
||||||
|
Microsoft.Build.Framework (>= 17.2) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= net472)) (&& (== netstandard2.0) (>= net6.0))
|
||||||
|
Microsoft.NET.StringTools (>= 1.0) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= net472)) (&& (== netstandard2.0) (>= net6.0))
|
||||||
|
Microsoft.Win32.Registry (>= 4.3) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= net6.0))
|
||||||
|
System.Collections.Immutable (>= 5.0) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= net472)) (&& (== netstandard2.0) (>= net6.0))
|
||||||
|
System.Configuration.ConfigurationManager (>= 4.7) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= net472)) (&& (== netstandard2.0) (>= net6.0))
|
||||||
|
System.Reflection.Metadata (>= 1.6) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= net6.0))
|
||||||
|
System.Security.Principal.Windows (>= 4.7) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= net6.0))
|
||||||
|
System.Text.Encoding.CodePages (>= 4.0.1) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= net6.0))
|
||||||
|
System.Text.Json (>= 6.0) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= net472)) (&& (== netstandard2.0) (>= net6.0))
|
||||||
|
System.Threading.Tasks.Dataflow (>= 6.0) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= net472)) (&& (== netstandard2.0) (>= net6.0))
|
||||||
|
Microsoft.Build.Framework (17.2)
|
||||||
|
Microsoft.Win32.Registry (>= 4.3)
|
||||||
|
System.Security.Permissions (>= 4.7)
|
||||||
|
Microsoft.Build.Tasks.Core (17.2)
|
||||||
|
Microsoft.Build.Framework (>= 17.2)
|
||||||
|
Microsoft.Build.Utilities.Core (>= 17.2)
|
||||||
|
Microsoft.NET.StringTools (>= 1.0)
|
||||||
|
Microsoft.Win32.Registry (>= 4.3)
|
||||||
|
System.CodeDom (>= 4.4)
|
||||||
|
System.Collections.Immutable (>= 5.0)
|
||||||
|
System.Reflection.Metadata (>= 1.6)
|
||||||
|
System.Resources.Extensions (>= 4.6)
|
||||||
|
System.Security.Cryptography.Pkcs (>= 4.7)
|
||||||
|
System.Security.Cryptography.Xml (>= 4.7)
|
||||||
|
System.Security.Permissions (>= 4.7)
|
||||||
|
System.Threading.Tasks.Dataflow (>= 6.0)
|
||||||
|
Microsoft.Build.Utilities.Core (17.2)
|
||||||
|
Microsoft.Build.Framework (>= 17.2)
|
||||||
|
Microsoft.NET.StringTools (>= 1.0)
|
||||||
|
Microsoft.Win32.Registry (>= 4.3)
|
||||||
|
System.Collections.Immutable (>= 5.0)
|
||||||
|
System.Configuration.ConfigurationManager (>= 4.7)
|
||||||
|
System.Security.Permissions (>= 4.7) - restriction: == netstandard2.0
|
||||||
|
System.Text.Encoding.CodePages (>= 4.0.1) - restriction: == netstandard2.0
|
||||||
|
Microsoft.NET.StringTools (1.0)
|
||||||
|
System.Memory (>= 4.5.4)
|
||||||
|
System.Runtime.CompilerServices.Unsafe (>= 5.0)
|
||||||
|
Microsoft.NETCore.Platforms (6.0.4) - restriction: || (&& (== net6.0) (< netcoreapp3.1)) (&& (== net6.0) (< netstandard1.2)) (&& (== net6.0) (< netstandard1.3)) (&& (== net6.0) (< netstandard1.5)) (== netstandard2.0)
|
||||||
|
Microsoft.NETCore.Targets (5.0) - restriction: || (&& (== net6.0) (< netcoreapp3.1)) (&& (== net6.0) (< netstandard1.2)) (&& (== net6.0) (< netstandard1.3)) (&& (== net6.0) (< netstandard1.5)) (== netstandard2.0)
|
||||||
|
Microsoft.Win32.Registry (5.0)
|
||||||
|
System.Buffers (>= 4.5.1) - restriction: || (&& (== net6.0) (>= monoandroid) (< netstandard1.3)) (&& (== net6.0) (>= monotouch)) (&& (== net6.0) (< netcoreapp2.0)) (&& (== net6.0) (>= xamarinios)) (&& (== net6.0) (>= xamarinmac)) (&& (== net6.0) (>= xamarintvos)) (&& (== net6.0) (>= xamarinwatchos)) (== netstandard2.0)
|
||||||
|
System.Memory (>= 4.5.4) - restriction: || (&& (== net6.0) (< netcoreapp2.0)) (&& (== net6.0) (< netcoreapp2.1)) (&& (== net6.0) (>= uap10.1)) (== netstandard2.0)
|
||||||
|
System.Security.AccessControl (>= 5.0)
|
||||||
|
System.Security.Principal.Windows (>= 5.0)
|
||||||
|
Microsoft.Win32.SystemEvents (6.0.1) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= netcoreapp3.1))
|
||||||
|
Mono.Posix.NETStandard (1.0)
|
||||||
|
MSBuild.StructuredLogger (2.1.669)
|
||||||
|
Microsoft.Build (>= 16.10)
|
||||||
|
Microsoft.Build.Framework (>= 16.10)
|
||||||
|
Microsoft.Build.Tasks.Core (>= 16.10)
|
||||||
|
Microsoft.Build.Utilities.Core (>= 16.10)
|
||||||
|
Newtonsoft.Json (13.0.1)
|
||||||
|
NuGet.Common (6.2.1)
|
||||||
|
NuGet.Frameworks (>= 6.2.1)
|
||||||
|
NuGet.Configuration (6.2.1)
|
||||||
|
NuGet.Common (>= 6.2.1)
|
||||||
|
System.Security.Cryptography.ProtectedData (>= 4.4)
|
||||||
|
NuGet.Frameworks (6.2.1)
|
||||||
|
NuGet.Packaging (6.2.1)
|
||||||
|
Newtonsoft.Json (>= 13.0.1)
|
||||||
|
NuGet.Configuration (>= 6.2.1)
|
||||||
|
NuGet.Versioning (>= 6.2.1)
|
||||||
|
System.Security.Cryptography.Cng (>= 5.0)
|
||||||
|
System.Security.Cryptography.Pkcs (>= 5.0)
|
||||||
|
NuGet.Protocol (6.2.1)
|
||||||
|
NuGet.Packaging (>= 6.2.1)
|
||||||
|
NuGet.Versioning (6.2.1)
|
||||||
|
System.Buffers (4.5.1) - restriction: || (&& (== net6.0) (>= monoandroid) (< netstandard1.3)) (&& (== net6.0) (>= monotouch)) (&& (== net6.0) (< netcoreapp2.0)) (&& (== net6.0) (>= xamarinios)) (&& (== net6.0) (>= xamarinmac)) (&& (== net6.0) (>= xamarintvos)) (&& (== net6.0) (>= xamarinwatchos)) (== netstandard2.0)
|
||||||
|
System.CodeDom (6.0)
|
||||||
|
System.Collections.Immutable (6.0)
|
||||||
|
System.Memory (>= 4.5.4) - restriction: || (&& (== net6.0) (>= net461)) (== netstandard2.0)
|
||||||
|
System.Runtime.CompilerServices.Unsafe (>= 6.0)
|
||||||
|
System.Configuration.ConfigurationManager (6.0)
|
||||||
|
System.Security.Cryptography.ProtectedData (>= 6.0)
|
||||||
|
System.Security.Permissions (>= 6.0)
|
||||||
|
System.Drawing.Common (6.0) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= netcoreapp3.1))
|
||||||
|
Microsoft.Win32.SystemEvents (>= 6.0) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= netcoreapp3.1))
|
||||||
|
System.Formats.Asn1 (6.0)
|
||||||
|
System.Buffers (>= 4.5.1) - restriction: || (&& (== net6.0) (>= net461)) (== netstandard2.0)
|
||||||
|
System.Memory (>= 4.5.4) - restriction: || (&& (== net6.0) (>= net461)) (== netstandard2.0)
|
||||||
|
System.Memory (4.5.5)
|
||||||
|
System.Buffers (>= 4.5.1) - restriction: || (&& (== net6.0) (>= monotouch)) (&& (== net6.0) (>= net461)) (&& (== net6.0) (< netcoreapp2.0)) (&& (== net6.0) (< netstandard1.1)) (&& (== net6.0) (< netstandard2.0)) (&& (== net6.0) (>= xamarinios)) (&& (== net6.0) (>= xamarinmac)) (&& (== net6.0) (>= xamarintvos)) (&& (== net6.0) (>= xamarinwatchos)) (== netstandard2.0)
|
||||||
|
System.Numerics.Vectors (>= 4.4) - restriction: || (&& (== net6.0) (< netcoreapp2.0)) (== netstandard2.0)
|
||||||
|
System.Runtime.CompilerServices.Unsafe (>= 4.5.3) - restriction: || (&& (== net6.0) (>= monotouch)) (&& (== net6.0) (>= net461)) (&& (== net6.0) (< netcoreapp2.0)) (&& (== net6.0) (< netcoreapp2.1)) (&& (== net6.0) (< netstandard1.1)) (&& (== net6.0) (< netstandard2.0)) (&& (== net6.0) (>= uap10.1)) (&& (== net6.0) (>= xamarinios)) (&& (== net6.0) (>= xamarinmac)) (&& (== net6.0) (>= xamarintvos)) (&& (== net6.0) (>= xamarinwatchos)) (== netstandard2.0)
|
||||||
|
System.Numerics.Vectors (4.5) - restriction: || (&& (== net6.0) (>= net461)) (== netstandard2.0)
|
||||||
|
System.Reactive (5.0)
|
||||||
|
System.Runtime.InteropServices.WindowsRuntime (>= 4.3) - restriction: || (&& (== net6.0) (< netcoreapp3.1)) (== netstandard2.0)
|
||||||
|
System.Threading.Tasks.Extensions (>= 4.5.4) - restriction: || (&& (== net6.0) (>= net472)) (&& (== net6.0) (< netcoreapp3.1)) (&& (== net6.0) (>= uap10.1)) (== netstandard2.0)
|
||||||
|
System.Reflection.Metadata (6.0.1)
|
||||||
|
System.Collections.Immutable (>= 6.0)
|
||||||
|
System.Resources.Extensions (6.0)
|
||||||
|
System.Memory (>= 4.5.4) - restriction: || (&& (== net6.0) (>= net461)) (== netstandard2.0)
|
||||||
|
System.Runtime (4.3.1) - restriction: || (&& (== net6.0) (< netcoreapp3.1)) (== netstandard2.0)
|
||||||
|
Microsoft.NETCore.Platforms (>= 1.1.1)
|
||||||
|
Microsoft.NETCore.Targets (>= 1.1.3)
|
||||||
|
System.Runtime.CompilerServices.Unsafe (6.0)
|
||||||
|
System.Runtime.InteropServices.WindowsRuntime (4.3) - restriction: || (&& (== net6.0) (< netcoreapp3.1)) (== netstandard2.0)
|
||||||
|
System.Runtime (>= 4.3)
|
||||||
|
System.Security.AccessControl (6.0)
|
||||||
|
System.Security.Principal.Windows (>= 5.0) - restriction: || (&& (== net6.0) (>= net461)) (== netstandard2.0)
|
||||||
|
System.Security.Cryptography.Cng (5.0)
|
||||||
|
System.Formats.Asn1 (>= 5.0) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= netcoreapp3.0))
|
||||||
|
System.Security.Cryptography.Pkcs (6.0.1)
|
||||||
|
System.Buffers (>= 4.5.1) - restriction: || (&& (== net6.0) (< netstandard2.1)) (== netstandard2.0)
|
||||||
|
System.Formats.Asn1 (>= 6.0)
|
||||||
|
System.Memory (>= 4.5.4) - restriction: || (&& (== net6.0) (< netstandard2.1)) (== netstandard2.0)
|
||||||
|
System.Security.Cryptography.Cng (>= 5.0) - restriction: || (&& (== net6.0) (< netcoreapp3.1)) (&& (== net6.0) (< netstandard2.1)) (== netstandard2.0)
|
||||||
|
System.Security.Cryptography.ProtectedData (6.0)
|
||||||
|
System.Security.Cryptography.Xml (6.0)
|
||||||
|
System.Memory (>= 4.5.4) - restriction: == netstandard2.0
|
||||||
|
System.Security.AccessControl (>= 6.0)
|
||||||
|
System.Security.Cryptography.Pkcs (>= 6.0)
|
||||||
|
System.Security.Permissions (6.0)
|
||||||
|
System.Security.AccessControl (>= 6.0)
|
||||||
|
System.Windows.Extensions (>= 6.0) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= netcoreapp3.1))
|
||||||
|
System.Security.Principal.Windows (5.0)
|
||||||
|
System.Text.Encoding.CodePages (6.0)
|
||||||
|
System.Runtime.CompilerServices.Unsafe (>= 6.0)
|
||||||
|
System.Text.Encodings.Web (6.0) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= net472)) (&& (== netstandard2.0) (>= net6.0))
|
||||||
|
System.Runtime.CompilerServices.Unsafe (>= 6.0)
|
||||||
|
System.Text.Json (6.0.5) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= net472)) (&& (== netstandard2.0) (>= net6.0))
|
||||||
|
System.Runtime.CompilerServices.Unsafe (>= 6.0)
|
||||||
|
System.Text.Encodings.Web (>= 6.0)
|
||||||
|
System.Threading.Tasks.Dataflow (6.0)
|
||||||
|
System.Threading.Tasks.Extensions (4.5.4) - restriction: || (&& (== net6.0) (>= net472)) (&& (== net6.0) (< netcoreapp3.1)) (&& (== net6.0) (>= uap10.1)) (== netstandard2.0)
|
||||||
|
System.Runtime.CompilerServices.Unsafe (>= 4.5.3) - restriction: || (&& (== net6.0) (>= net461)) (&& (== net6.0) (< netcoreapp2.1)) (&& (== net6.0) (< netstandard1.0)) (&& (== net6.0) (< netstandard2.0)) (&& (== net6.0) (>= wp8)) (== netstandard2.0)
|
||||||
|
System.Windows.Extensions (6.0) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= netcoreapp3.1))
|
||||||
|
System.Drawing.Common (>= 6.0) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= netcoreapp3.1))
|
7
fake.sh
Executable file
7
fake.sh
Executable file
|
@ -0,0 +1,7 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
dotnet tool restore
|
||||||
|
dotnet fake "$@"
|
|
@ -21,6 +21,7 @@ module.exports = {
|
||||||
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
|
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||||
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
|
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||||
"vue/no-multiple-template-root": "off",
|
"vue/no-multiple-template-root": "off",
|
||||||
|
"vue/multi-word-component-names": "off",
|
||||||
"vue/script-setup-uses-vars": 1,
|
"vue/script-setup-uses-vars": 1,
|
||||||
"quotes": ["error", "double", { avoidEscape: true, allowTemplateLiterals: true }],
|
"quotes": ["error", "double", { avoidEscape: true, allowTemplateLiterals: true }],
|
||||||
"func-call-spacing": "off",
|
"func-call-spacing": "off",
|
||||||
|
|
28264
src/JobsJobsJobs/App/package-lock.json
generated
28264
src/JobsJobsJobs/App/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -1,25 +1,23 @@
|
||||||
{
|
{
|
||||||
"name": "jobs-jobs-jobs",
|
"name": "jobs-jobs-jobs",
|
||||||
"version": "2.2.1",
|
"version": "2.2.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "vue-cli-service serve",
|
||||||
"build": "vue-cli-service build",
|
"build": "vue-cli-service build",
|
||||||
"lint": "vue-cli-service lint",
|
"lint": "vue-cli-service lint"
|
||||||
"apiserve": "vue-cli-service build && cd ../Server && dotnet run -c Debug",
|
|
||||||
"publish": "vue-cli-service build --modern && cd ../Server && dotnet publish -c Release -r linux-x64 -p:PublishSingleFile=true --self-contained false"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mdi/js": "^5.9.55",
|
"@mdi/js": "^6.9.96",
|
||||||
"@vuelidate/core": "^2.0.0-alpha.24",
|
"@vuelidate/core": "^2.0.0-alpha.24",
|
||||||
"@vuelidate/validators": "^2.0.0-alpha.21",
|
"@vuelidate/validators": "^2.0.0-alpha.21",
|
||||||
"@vueuse/core": "^6.3.3",
|
"@vueuse/core": "^8.9.1",
|
||||||
"bootstrap": "^5.1.0",
|
"bootstrap": "^5.1.0",
|
||||||
"core-js": "^3.16.3",
|
"core-js": "^3.16.3",
|
||||||
"date-fns": "^2.23.0",
|
"date-fns": "^2.23.0",
|
||||||
"date-fns-tz": "^1.1.6",
|
"date-fns-tz": "^1.1.6",
|
||||||
"dompurify": "^2.3.1",
|
"dompurify": "^2.3.1",
|
||||||
"marked": "^2.1.3",
|
"marked": "^4.0.18",
|
||||||
"vue": "^3.2.6",
|
"vue": "^3.2.6",
|
||||||
"vue-router": "^4.0.11",
|
"vue-router": "^4.0.11",
|
||||||
"vuex": "^4.0.0-0"
|
"vuex": "^4.0.0-0"
|
||||||
|
@ -27,27 +25,28 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bootstrap": "^5.1.2",
|
"@types/bootstrap": "^5.1.2",
|
||||||
"@types/dompurify": "^2.2.3",
|
"@types/dompurify": "^2.2.3",
|
||||||
"@types/marked": "^2.0.5",
|
"@types/marked": "^4.0.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.29.3",
|
"@typescript-eslint/eslint-plugin": "^5.30.0",
|
||||||
"@typescript-eslint/parser": "^4.29.3",
|
"@typescript-eslint/parser": "^5.30.0",
|
||||||
"@vue/cli-plugin-babel": "~4.5.0",
|
"@vue/cli-plugin-babel": "~5.0.0",
|
||||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
"@vue/cli-plugin-eslint": "~5.0.0",
|
||||||
"@vue/cli-plugin-router": "~4.5.0",
|
"@vue/cli-plugin-router": "~5.0.0",
|
||||||
"@vue/cli-plugin-typescript": "~4.5.0",
|
"@vue/cli-plugin-typescript": "~5.0.0",
|
||||||
"@vue/cli-plugin-vuex": "~4.5.0",
|
"@vue/cli-plugin-vuex": "~5.0.0",
|
||||||
"@vue/cli-service": "~4.5.0",
|
"@vue/cli-service": "~5.0.0",
|
||||||
"@vue/compiler-sfc": "^3.2.6",
|
"@vue/compiler-sfc": "^3.2.6",
|
||||||
"@vue/eslint-config-standard": "^6.1.0",
|
"@vue/eslint-config-standard": "^7.0.0",
|
||||||
"@vue/eslint-config-typescript": "^7.0.0",
|
"@vue/eslint-config-typescript": "^11.0.0",
|
||||||
"eslint": "^7.32.0",
|
"eslint": "^8.19.0",
|
||||||
"eslint-plugin-import": "^2.24.2",
|
"eslint-plugin-import": "^2.24.2",
|
||||||
|
"eslint-plugin-n": "^15.2.4",
|
||||||
"eslint-plugin-node": "^11.1.0",
|
"eslint-plugin-node": "^11.1.0",
|
||||||
"eslint-plugin-promise": "^5.0.0",
|
"eslint-plugin-promise": "^6.0.0",
|
||||||
"eslint-plugin-standard": "^5.0.0",
|
"eslint-plugin-standard": "^5.0.0",
|
||||||
"eslint-plugin-vue": "^7.17.0",
|
"eslint-plugin-vue": "^9.2.0",
|
||||||
"sass": "~1.37.0",
|
"sass": "~1.37.0",
|
||||||
"sass-loader": "^10.0.0",
|
"sass-loader": "^10.0.0",
|
||||||
"typescript": "~4.3.5",
|
"typescript": "~4.5.0",
|
||||||
"vue-cli-plugin-pug": "~2.0.0"
|
"vue-cli-plugin-pug": "~2.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,10 @@ export interface Instance {
|
||||||
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 enabled */
|
||||||
|
isEnabled : boolean
|
||||||
|
/** If disabled, the reason why it is disabled */
|
||||||
|
reason : string
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A job listing */
|
/** A job listing */
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { sanitize } from "dompurify"
|
import { sanitize } from "dompurify"
|
||||||
import marked from "marked"
|
import { marked } from "marked"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transform Markdown to HTML (standardize option, sanitize the output)
|
* Transform Markdown to HTML (standardize option, sanitize the output)
|
||||||
|
|
|
@ -5,7 +5,10 @@ article
|
||||||
template(v-else)
|
template(v-else)
|
||||||
p.text-center Please select your No Agenda-affiliated Mastodon instance
|
p.text-center Please select your No Agenda-affiliated Mastodon instance
|
||||||
p.text-center(v-for="it in instances" :key="it.abbr")
|
p.text-center(v-for="it in instances" :key="it.abbr")
|
||||||
|
template(v-if="it.isEnabled")
|
||||||
button.btn.btn-primary(@click.prevent="select(it.abbr)") {{it.name}}
|
button.btn.btn-primary(@click.prevent="select(it.abbr)") {{it.name}}
|
||||||
|
template(v-else).
|
||||||
|
#[button.btn.btn-secondary(disabled="disabled") {{it.name}}]#[br]#[em {{it.reason}}]
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AssemblyVersion>1.0.1.0</AssemblyVersion>
|
<DebugType>embedded</DebugType>
|
||||||
<FileVersion>1.0.1.0</FileVersion>
|
<AssemblyVersion>2.2.2.0</AssemblyVersion>
|
||||||
|
<FileVersion>2.2.2.0</FileVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<WarnOn>3390;$(WarnOn)</WarnOn>
|
<WarnOn>3390;$(WarnOn)</WarnOn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
@ -13,9 +12,10 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Markdig" Version="0.25.0" />
|
<PackageReference Include="Markdig" Version="0.30.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
|
||||||
<PackageReference Include="NodaTime" Version="3.0.5" />
|
<PackageReference Include="NodaTime" Version="3.1.0" />
|
||||||
|
<PackageReference Update="FSharp.Core" Version="6.0.5" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -7,16 +7,12 @@ open System
|
||||||
open Types
|
open Types
|
||||||
|
|
||||||
/// Format a GUID as a Short GUID
|
/// Format a GUID as a Short GUID
|
||||||
let private toShortGuid guid =
|
let private toShortGuid (guid : Guid) =
|
||||||
let convert (g : Guid) =
|
Convert.ToBase64String(guid.ToByteArray ()).Replace('/', '_').Replace('+', '-')[0..21]
|
||||||
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
|
/// Turn a Short GUID back into a GUID
|
||||||
let private fromShortGuid x =
|
let private fromShortGuid (it : string) =
|
||||||
let unBase64 = x |> String.map (fun x -> match x with '_' -> '/' | '-' -> '+' | _ -> x)
|
(Convert.FromBase64String >> Guid) $"{it.Replace('_', '/').Replace('-', '+')}=="
|
||||||
(Convert.FromBase64String >> Guid) $"{unBase64}=="
|
|
||||||
|
|
||||||
|
|
||||||
/// Support functions for citizen IDs
|
/// Support functions for citizen IDs
|
||||||
|
@ -24,7 +20,7 @@ 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
|
||||||
|
|
||||||
|
@ -43,7 +39,7 @@ 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
|
||||||
|
|
||||||
|
@ -53,7 +49,7 @@ 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
|
||||||
|
|
||||||
|
@ -63,7 +59,7 @@ 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)
|
||||||
|
|
||||||
|
|
||||||
/// Support functions for Profiles
|
/// Support functions for Profiles
|
||||||
|
@ -88,7 +84,7 @@ 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
|
||||||
|
|
||||||
|
@ -98,6 +94,6 @@ 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
|
||||||
|
|
|
@ -8,8 +8,8 @@ open NodaTime
|
||||||
// fsharplint:disable FieldNames
|
// fsharplint:disable FieldNames
|
||||||
|
|
||||||
/// The data required to add or edit a job listing
|
/// The data required to add or edit a job listing
|
||||||
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
|
||||||
|
@ -27,8 +27,8 @@ type ListingForm = {
|
||||||
|
|
||||||
|
|
||||||
/// The data needed to display a listing
|
/// The data needed to display a listing
|
||||||
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
|
||||||
|
@ -36,8 +36,8 @@ type ListingForView = {
|
||||||
|
|
||||||
|
|
||||||
/// The form submitted to expire a listing
|
/// The form submitted to expire a listing
|
||||||
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
|
||||||
|
@ -46,8 +46,8 @@ type ListingExpireForm = {
|
||||||
|
|
||||||
/// The various ways job listings can be searched
|
/// The various ways job listings can be searched
|
||||||
[<CLIMutable>]
|
[<CLIMutable>]
|
||||||
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
|
||||||
|
@ -59,8 +59,8 @@ type ListingSearch = {
|
||||||
|
|
||||||
|
|
||||||
/// A successful logon
|
/// A successful logon
|
||||||
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
|
||||||
|
@ -70,8 +70,8 @@ type LogOnSuccess = {
|
||||||
|
|
||||||
|
|
||||||
/// A count
|
/// A count
|
||||||
type Count = {
|
type Count =
|
||||||
// The count being returned
|
{ // The count being returned
|
||||||
count : int64
|
count : int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,11 +88,15 @@ type MastodonInstance () =
|
||||||
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
|
||||||
|
member val IsEnabled = true with get, set
|
||||||
|
/// If an instance is disabled, the reason for it being disabled
|
||||||
|
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 Mastodoon 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
|
||||||
|
@ -103,8 +107,8 @@ type AuthOptions () =
|
||||||
|
|
||||||
|
|
||||||
/// The Mastodon instance data provided via the Jobs, Jobs, Jobs API
|
/// The Mastodon instance data provided via the Jobs, Jobs, Jobs API
|
||||||
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
|
||||||
|
@ -112,12 +116,16 @@ type Instance = {
|
||||||
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
|
||||||
|
isEnabled : bool
|
||||||
|
/// If not enabled, the reason the instance is disabled
|
||||||
|
reason : string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// The fields required for a skill
|
/// The fields required for a skill
|
||||||
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
|
||||||
|
@ -127,8 +135,8 @@ type SkillForm = {
|
||||||
|
|
||||||
/// The data required to update a profile
|
/// The data required to update a profile
|
||||||
[<CLIMutable; NoComparison; NoEquality>]
|
[<CLIMutable; NoComparison; NoEquality>]
|
||||||
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
|
||||||
|
@ -174,8 +182,8 @@ module ProfileForm =
|
||||||
|
|
||||||
/// The various ways profiles can be searched
|
/// The various ways profiles can be searched
|
||||||
[<CLIMutable>]
|
[<CLIMutable>]
|
||||||
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
|
||||||
|
@ -187,8 +195,8 @@ type ProfileSearch = {
|
||||||
|
|
||||||
|
|
||||||
/// A user matching the profile search
|
/// A user matching the profile search
|
||||||
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
|
||||||
|
@ -204,8 +212,8 @@ type ProfileSearchResult = {
|
||||||
|
|
||||||
|
|
||||||
/// The data required to show a viewable profile
|
/// The data required to show a viewable profile
|
||||||
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
|
||||||
|
@ -216,8 +224,8 @@ type ProfileForView = {
|
||||||
|
|
||||||
/// The parameters for a public job search
|
/// The parameters for a public job search
|
||||||
[<CLIMutable>]
|
[<CLIMutable>]
|
||||||
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
|
||||||
|
@ -227,20 +235,20 @@ type PublicSearch = {
|
||||||
remoteWork : string
|
remoteWork : string
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Support functions for pblic searches
|
/// Support functions for public searches
|
||||||
module PublicSearch =
|
module PublicSearch =
|
||||||
/// Is the search empty?
|
/// Is the search empty?
|
||||||
let isEmptySearch (srch : PublicSearch) =
|
let isEmptySearch (search : PublicSearch) =
|
||||||
[ srch.continentId
|
[ search.continentId
|
||||||
srch.skill
|
search.skill
|
||||||
match srch.remoteWork with "" -> Some srch.remoteWork | _ -> None
|
match search.remoteWork with "" -> Some search.remoteWork | _ -> None
|
||||||
]
|
]
|
||||||
|> List.exists Option.isSome
|
|> List.exists Option.isSome
|
||||||
|
|
||||||
|
|
||||||
/// A public profile search result
|
/// A public profile search result
|
||||||
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
|
||||||
|
@ -252,8 +260,8 @@ type PublicSearchResult = {
|
||||||
|
|
||||||
|
|
||||||
/// The data required to provide a success story
|
/// The data required to provide a success story
|
||||||
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
|
||||||
|
@ -263,8 +271,8 @@ type StoryForm = {
|
||||||
|
|
||||||
|
|
||||||
/// An entry in the list of success stories
|
/// An entry in the list of success stories
|
||||||
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
|
||||||
|
|
|
@ -11,8 +11,8 @@ type CitizenId = CitizenId of Guid
|
||||||
|
|
||||||
/// A user of Jobs, Jobs, Jobs
|
/// A user of Jobs, Jobs, Jobs
|
||||||
[<CLIMutable; NoComparison; NoEquality>]
|
[<CLIMutable; NoComparison; NoEquality>]
|
||||||
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
|
||||||
|
@ -36,8 +36,8 @@ type ContinentId = ContinentId of Guid
|
||||||
|
|
||||||
/// A continent
|
/// A continent
|
||||||
[<CLIMutable; NoComparison; NoEquality>]
|
[<CLIMutable; NoComparison; NoEquality>]
|
||||||
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
|
||||||
|
@ -53,8 +53,8 @@ type ListingId = ListingId of Guid
|
||||||
|
|
||||||
/// A job listing
|
/// A job listing
|
||||||
[<CLIMutable; NoComparison; NoEquality>]
|
[<CLIMutable; NoComparison; NoEquality>]
|
||||||
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
|
||||||
|
@ -85,8 +85,8 @@ type Listing = {
|
||||||
type SkillId = SkillId of Guid
|
type SkillId = SkillId of Guid
|
||||||
|
|
||||||
/// A skill the job seeker possesses
|
/// A skill the job seeker possesses
|
||||||
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
|
||||||
|
@ -97,8 +97,8 @@ type Skill = {
|
||||||
|
|
||||||
/// A job seeker profile
|
/// A job seeker profile
|
||||||
[<CLIMutable; NoComparison; NoEquality>]
|
[<CLIMutable; NoComparison; NoEquality>]
|
||||||
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
|
||||||
|
@ -127,8 +127,8 @@ type SuccessId = SuccessId of Guid
|
||||||
|
|
||||||
/// A record of success finding employment
|
/// A record of success finding employment
|
||||||
[<CLIMutable; NoComparison; NoEquality>]
|
[<CLIMutable; NoComparison; NoEquality>]
|
||||||
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
|
||||||
|
|
|
@ -11,8 +11,7 @@ open Giraffe.EndpointRouting
|
||||||
|
|
||||||
/// Configure the ASP.NET Core pipeline to use Giraffe
|
/// Configure the ASP.NET Core pipeline to use Giraffe
|
||||||
let configureApp (app : IApplicationBuilder) =
|
let configureApp (app : IApplicationBuilder) =
|
||||||
app
|
app.UseCors(fun p -> p.AllowAnyOrigin().AllowAnyHeader() |> ignore)
|
||||||
.UseCors(fun p -> p.AllowAnyOrigin().AllowAnyHeader() |> ignore)
|
|
||||||
.UseStaticFiles()
|
.UseStaticFiles()
|
||||||
.UseRouting()
|
.UseRouting()
|
||||||
.UseAuthentication()
|
.UseAuthentication()
|
||||||
|
@ -64,16 +63,15 @@ let configureServices (svc : IServiceCollection) =
|
||||||
svc.Configure<AuthOptions> (cfg.GetSection "Auth") |> ignore
|
svc.Configure<AuthOptions> (cfg.GetSection "Auth") |> ignore
|
||||||
|
|
||||||
let dbCfg = cfg.GetSection "Rethink"
|
let dbCfg = cfg.GetSection "Rethink"
|
||||||
let log = svcs.GetRequiredService<ILoggerFactory>().CreateLogger (nameof Data.Startup)
|
let log = svcs.GetRequiredService<ILoggerFactory>().CreateLogger "JobsJobsJobs.Api.Data.Startup"
|
||||||
let conn = Data.Startup.createConnection dbCfg log
|
let conn = Data.Startup.createConnection dbCfg log
|
||||||
svc.AddSingleton conn |> ignore
|
svc.AddSingleton conn |> ignore
|
||||||
Data.Startup.establishEnvironment dbCfg log conn |> Data.awaitIgnore
|
Data.Startup.establishEnvironment dbCfg log conn |> Async.AwaitTask |> Async.RunSynchronously
|
||||||
|
|
||||||
[<EntryPoint>]
|
[<EntryPoint>]
|
||||||
let main _ =
|
let main _ =
|
||||||
Host.CreateDefaultBuilder()
|
Host.CreateDefaultBuilder()
|
||||||
.ConfigureWebHostDefaults(
|
.ConfigureWebHostDefaults(fun webHostBuilder ->
|
||||||
fun webHostBuilder ->
|
|
||||||
webHostBuilder
|
webHostBuilder
|
||||||
.Configure(configureApp)
|
.Configure(configureApp)
|
||||||
.ConfigureServices(configureServices)
|
.ConfigureServices(configureServices)
|
||||||
|
|
|
@ -99,7 +99,8 @@ let createJwt (citizen : Citizen) (cfg : AuthOptions) =
|
||||||
Issuer = "https://noagendacareers.com",
|
Issuer = "https://noagendacareers.com",
|
||||||
Audience = "https://noagendacareers.com",
|
Audience = "https://noagendacareers.com",
|
||||||
SigningCredentials = SigningCredentials (
|
SigningCredentials = SigningCredentials (
|
||||||
SymmetricSecurityKey (Encoding.UTF8.GetBytes cfg.ServerSecret), SecurityAlgorithms.HmacSha256Signature)
|
SymmetricSecurityKey (
|
||||||
|
Encoding.UTF8.GetBytes cfg.ServerSecret), SecurityAlgorithms.HmacSha256Signature)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
tokenHandler.WriteToken token
|
tokenHandler.WriteToken token
|
||||||
|
|
|
@ -2,17 +2,6 @@
|
||||||
module JobsJobsJobs.Api.Data
|
module JobsJobsJobs.Api.Data
|
||||||
|
|
||||||
open JobsJobsJobs.Domain.Types
|
open JobsJobsJobs.Domain.Types
|
||||||
open Polly
|
|
||||||
open RethinkDb.Driver
|
|
||||||
open RethinkDb.Driver.Net
|
|
||||||
open RethinkDb.Driver.Ast
|
|
||||||
|
|
||||||
/// 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
|
/// JSON converters used with RethinkDB persistence
|
||||||
module Converters =
|
module Converters =
|
||||||
|
@ -25,61 +14,61 @@ module Converters =
|
||||||
/// JSON converter for citizen IDs
|
/// JSON converter for citizen IDs
|
||||||
type CitizenIdJsonConverter() =
|
type CitizenIdJsonConverter() =
|
||||||
inherit JsonConverter<CitizenId>()
|
inherit JsonConverter<CitizenId>()
|
||||||
override __.WriteJson(writer : JsonWriter, value : CitizenId, _ : JsonSerializer) =
|
override _.WriteJson(writer : JsonWriter, value : CitizenId, _ : JsonSerializer) =
|
||||||
writer.WriteValue (CitizenId.toString value)
|
writer.WriteValue (CitizenId.toString value)
|
||||||
override __.ReadJson(reader: JsonReader, _ : Type, _ : CitizenId, _ : bool, _ : JsonSerializer) =
|
override _.ReadJson(reader: JsonReader, _ : Type, _ : CitizenId, _ : bool, _ : JsonSerializer) =
|
||||||
(string >> CitizenId.ofString) reader.Value
|
(string >> CitizenId.ofString) reader.Value
|
||||||
|
|
||||||
/// JSON converter for continent IDs
|
/// JSON converter for continent IDs
|
||||||
type ContinentIdJsonConverter() =
|
type ContinentIdJsonConverter() =
|
||||||
inherit JsonConverter<ContinentId>()
|
inherit JsonConverter<ContinentId>()
|
||||||
override __.WriteJson(writer : JsonWriter, value : ContinentId, _ : JsonSerializer) =
|
override _.WriteJson(writer : JsonWriter, value : ContinentId, _ : JsonSerializer) =
|
||||||
writer.WriteValue (ContinentId.toString value)
|
writer.WriteValue (ContinentId.toString value)
|
||||||
override __.ReadJson(reader: JsonReader, _ : Type, _ : ContinentId, _ : bool, _ : JsonSerializer) =
|
override _.ReadJson(reader: JsonReader, _ : Type, _ : ContinentId, _ : bool, _ : JsonSerializer) =
|
||||||
(string >> ContinentId.ofString) reader.Value
|
(string >> ContinentId.ofString) reader.Value
|
||||||
|
|
||||||
/// JSON converter for Markdown strings
|
/// JSON converter for Markdown strings
|
||||||
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
|
let (Text text) = value
|
||||||
writer.WriteValue text
|
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
|
||||||
|
|
||||||
/// JSON converter for listing IDs
|
/// JSON converter for listing IDs
|
||||||
type ListingIdJsonConverter() =
|
type ListingIdJsonConverter() =
|
||||||
inherit JsonConverter<ListingId>()
|
inherit JsonConverter<ListingId>()
|
||||||
override __.WriteJson(writer : JsonWriter, value : ListingId, _ : JsonSerializer) =
|
override _.WriteJson(writer : JsonWriter, value : ListingId, _ : JsonSerializer) =
|
||||||
writer.WriteValue (ListingId.toString value)
|
writer.WriteValue (ListingId.toString value)
|
||||||
override __.ReadJson(reader: JsonReader, _ : Type, _ : ListingId, _ : bool, _ : JsonSerializer) =
|
override _.ReadJson(reader: JsonReader, _ : Type, _ : ListingId, _ : bool, _ : JsonSerializer) =
|
||||||
(string >> ListingId.ofString) reader.Value
|
(string >> ListingId.ofString) reader.Value
|
||||||
|
|
||||||
/// JSON converter for skill IDs
|
/// JSON converter for skill IDs
|
||||||
type SkillIdJsonConverter() =
|
type SkillIdJsonConverter() =
|
||||||
inherit JsonConverter<SkillId>()
|
inherit JsonConverter<SkillId>()
|
||||||
override __.WriteJson(writer : JsonWriter, value : SkillId, _ : JsonSerializer) =
|
override _.WriteJson(writer : JsonWriter, value : SkillId, _ : JsonSerializer) =
|
||||||
writer.WriteValue (SkillId.toString value)
|
writer.WriteValue (SkillId.toString value)
|
||||||
override __.ReadJson(reader: JsonReader, _ : Type, _ : SkillId, _ : bool, _ : JsonSerializer) =
|
override _.ReadJson(reader: JsonReader, _ : Type, _ : SkillId, _ : bool, _ : JsonSerializer) =
|
||||||
(string >> SkillId.ofString) reader.Value
|
(string >> SkillId.ofString) reader.Value
|
||||||
|
|
||||||
/// JSON converter for success report IDs
|
/// JSON converter for success report IDs
|
||||||
type SuccessIdJsonConverter() =
|
type SuccessIdJsonConverter() =
|
||||||
inherit JsonConverter<SuccessId>()
|
inherit JsonConverter<SuccessId>()
|
||||||
override __.WriteJson(writer : JsonWriter, value : SuccessId, _ : JsonSerializer) =
|
override _.WriteJson(writer : JsonWriter, value : SuccessId, _ : JsonSerializer) =
|
||||||
writer.WriteValue (SuccessId.toString value)
|
writer.WriteValue (SuccessId.toString value)
|
||||||
override __.ReadJson(reader: JsonReader, _ : Type, _ : SuccessId, _ : bool, _ : JsonSerializer) =
|
override _.ReadJson(reader: JsonReader, _ : Type, _ : SuccessId, _ : bool, _ : JsonSerializer) =
|
||||||
(string >> SuccessId.ofString) reader.Value
|
(string >> SuccessId.ofString) reader.Value
|
||||||
|
|
||||||
/// All JSON converters needed for the application
|
/// All JSON converters needed for the application
|
||||||
let all () = [
|
let all () : JsonConverter list =
|
||||||
CitizenIdJsonConverter () :> JsonConverter
|
[ CitizenIdJsonConverter ()
|
||||||
upcast ContinentIdJsonConverter ()
|
ContinentIdJsonConverter ()
|
||||||
upcast MarkdownStringJsonConverter ()
|
MarkdownStringJsonConverter ()
|
||||||
upcast ListingIdJsonConverter ()
|
ListingIdJsonConverter ()
|
||||||
upcast SkillIdJsonConverter ()
|
SkillIdJsonConverter ()
|
||||||
upcast SuccessIdJsonConverter ()
|
SuccessIdJsonConverter ()
|
||||||
upcast CompactUnionJsonConverter ()
|
CompactUnionJsonConverter ()
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -100,6 +89,28 @@ module Table =
|
||||||
let all () = [ Citizen; Continent; Listing; Profile; Success ]
|
let all () = [ Citizen; Continent; Listing; Profile; Success ]
|
||||||
|
|
||||||
|
|
||||||
|
open RethinkDb.Driver.FSharp.Functions
|
||||||
|
open RethinkDb.Driver.Net
|
||||||
|
|
||||||
|
/// Reconnection functions (if the RethinkDB driver has a network error, it will not reconnect on its own)
|
||||||
|
[<AutoOpen>]
|
||||||
|
module private Reconnect =
|
||||||
|
|
||||||
|
/// Retrieve a result using the F# driver's default retry policy
|
||||||
|
let result<'T> conn expr = runResult<'T> expr |> withRetryDefault |> withConn conn
|
||||||
|
|
||||||
|
/// Retrieve an optional result using the F# driver's default retry policy
|
||||||
|
let resultOption<'T> conn expr = runResult<'T> expr |> withRetryDefault |> asOption |> withConn conn
|
||||||
|
|
||||||
|
/// Write a query using the F# driver's default retry policy, ignoring the result
|
||||||
|
let write conn expr = runWrite expr |> withRetryDefault |> ignoreResult |> withConn conn
|
||||||
|
|
||||||
|
|
||||||
|
open RethinkDb.Driver.Ast
|
||||||
|
|
||||||
|
/// Shorthand for the RethinkDB R variable (how every command starts)
|
||||||
|
let private r = RethinkDb.Driver.RethinkDB.R
|
||||||
|
|
||||||
/// Functions run at startup
|
/// Functions run at startup
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module Startup =
|
module Startup =
|
||||||
|
@ -108,180 +119,113 @@ module Startup =
|
||||||
open Microsoft.Extensions.Logging
|
open Microsoft.Extensions.Logging
|
||||||
open NodaTime
|
open NodaTime
|
||||||
open NodaTime.Serialization.JsonNet
|
open NodaTime.Serialization.JsonNet
|
||||||
|
open RethinkDb.Driver.FSharp
|
||||||
|
|
||||||
/// Create a RethinkDB connection
|
/// Create a RethinkDB connection
|
||||||
let createConnection (cfg : IConfigurationSection) (log : ILogger) =
|
let createConnection (cfg : IConfigurationSection) (log : ILogger) =
|
||||||
|
|
||||||
// Add all required JSON converters
|
// Add all required JSON converters
|
||||||
Converter.Serializer.ConfigureForNodaTime DateTimeZoneProviders.Tzdb |> ignore
|
Converter.Serializer.ConfigureForNodaTime DateTimeZoneProviders.Tzdb |> ignore
|
||||||
Converters.all ()
|
Converters.all ()
|
||||||
|> List.iter Converter.Serializer.Converters.Add
|
|> List.iter Converter.Serializer.Converters.Add
|
||||||
// Read the configuration and create a connection
|
// Connect to the database
|
||||||
let bldr =
|
let config = DataConfig.FromConfiguration cfg
|
||||||
seq<Connection.Builder -> Connection.Builder> {
|
log.LogInformation $"Connecting to rethinkdb://{config.Hostname}:{config.Port}/{config.Database}"
|
||||||
yield fun b -> match cfg.["Hostname"] with null -> b | host -> b.Hostname host
|
config.CreateConnection ()
|
||||||
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
|
/// Ensure the data, tables, and indexes that are required exist
|
||||||
let establishEnvironment (cfg : IConfigurationSection) (log : ILogger) conn = task {
|
let establishEnvironment (cfg : IConfigurationSection) (log : ILogger) conn = task {
|
||||||
// Ensure the database exists
|
// Ensure the database exists
|
||||||
match cfg.["Db"] |> Option.ofObj with
|
match cfg["database"] |> Option.ofObj with
|
||||||
| Some database ->
|
| Some database ->
|
||||||
let! dbs = r.DbList().RunResultAsync<string list> conn
|
let! dbs = dbList () |> result<string list> conn
|
||||||
match dbs |> List.contains database with
|
match dbs |> List.contains database with
|
||||||
| true -> ()
|
| true -> ()
|
||||||
| false ->
|
| false ->
|
||||||
log.LogInformation $"Creating database {database}..."
|
log.LogInformation $"Creating database {database}..."
|
||||||
let! _ = r.DbCreate(database).RunWriteAsync conn
|
do! dbCreate database |> write conn
|
||||||
()
|
()
|
||||||
| None -> ()
|
| None -> ()
|
||||||
// Ensure the tables exist
|
// Ensure the tables exist
|
||||||
let! tables = r.TableList().RunResultAsync<string list> conn
|
let! tables = tableListFromDefault () |> result<string list> conn
|
||||||
Table.all ()
|
for table in Table.all () do
|
||||||
|> List.iter (
|
if not (List.contains table tables) then
|
||||||
fun tbl ->
|
log.LogInformation $"Creating {table} table..."
|
||||||
match tables |> List.contains tbl with
|
do! tableCreateInDefault table |> write conn
|
||||||
| true -> ()
|
|
||||||
| false ->
|
|
||||||
log.LogInformation $"Creating {tbl} table..."
|
|
||||||
r.TableCreate(tbl).RunWriteAsync conn |> awaitIgnore)
|
|
||||||
// Ensure the indexes exist
|
// Ensure the indexes exist
|
||||||
let ensureIndexes table indexes = task {
|
let ensureIndexes table indexes = task {
|
||||||
let! tblIdxs = r.Table(table).IndexList().RunResultAsync<string list> conn
|
let! tblIndexes = fromTable table |> indexList |> result<string list> conn
|
||||||
indexes
|
for index in indexes do
|
||||||
|> List.iter (
|
if not (List.contains index tblIndexes) then
|
||||||
fun idx ->
|
log.LogInformation $"Creating \"{index}\" index on {table}"
|
||||||
match tblIdxs |> List.contains idx with
|
do! fromTable table |> indexCreate index |> write conn
|
||||||
| true -> ()
|
|
||||||
| false ->
|
|
||||||
log.LogInformation $"Creating \"{idx}\" index on {table}"
|
|
||||||
r.Table(table).IndexCreate(idx).RunWriteAsync conn |> awaitIgnore)
|
|
||||||
}
|
}
|
||||||
do! ensureIndexes Table.Listing [ "citizenId"; "continentId"; "isExpired" ]
|
do! ensureIndexes Table.Listing [ "citizenId"; "continentId"; "isExpired" ]
|
||||||
do! ensureIndexes Table.Profile [ "continentId" ]
|
do! ensureIndexes Table.Profile [ "continentId" ]
|
||||||
do! ensureIndexes Table.Success [ "citizenId" ]
|
do! ensureIndexes Table.Success [ "citizenId" ]
|
||||||
// The instance/user is a compound index
|
// The instance/user is a compound index
|
||||||
let! userIdx = r.Table(Table.Citizen).IndexList().RunResultAsync<string list> conn
|
let! userIdx = fromTable Table.Citizen |> indexList |> result<string list> conn
|
||||||
match userIdx |> List.contains "instanceUser" with
|
if not (List.contains "instanceUser" userIdx) then
|
||||||
| true -> ()
|
do! fromTable Table.Citizen
|
||||||
| false ->
|
|> indexCreateFunc "instanceUser" (fun row -> r.Array (row.G "instance", row.G "mastodonUser"))
|
||||||
let! _ =
|
|> write conn
|
||||||
r.Table(Table.Citizen)
|
|
||||||
.IndexCreate("instanceUser",
|
|
||||||
ReqlFunction1 (fun row -> upcast r.Array (row.G "instance", row.G "mastodonUser")))
|
|
||||||
.RunWriteAsync conn
|
|
||||||
()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Determine if a record type (not nullable) is null
|
|
||||||
let toOption x = match x |> box |> isNull with true -> None | false -> Some x
|
|
||||||
|
|
||||||
[<AutoOpen>]
|
|
||||||
module private Reconnect =
|
|
||||||
|
|
||||||
open System.Threading.Tasks
|
|
||||||
|
|
||||||
/// Execute a query with a retry policy that will reconnect to RethinkDB if it has gone away
|
|
||||||
let withReconn (conn : IConnection) (f : IConnection -> Task<'T>) =
|
|
||||||
Policy
|
|
||||||
.Handle<ReqlDriverError>()
|
|
||||||
.RetryAsync(System.Action<exn, int> (fun ex _ ->
|
|
||||||
printf "Encountered RethinkDB exception: %s" ex.Message
|
|
||||||
match ex.Message.Contains "socket" with
|
|
||||||
| true ->
|
|
||||||
printf "Reconnecting to RethinkDB"
|
|
||||||
(conn :?> Connection).Reconnect false
|
|
||||||
| false -> ()))
|
|
||||||
.ExecuteAsync(fun () -> f conn)
|
|
||||||
|
|
||||||
/// Execute a query that returns one or none item, using the reconnect logic
|
|
||||||
let withReconnOption (conn : IConnection) (f : IConnection -> Task<'T>) =
|
|
||||||
fun c -> task {
|
|
||||||
let! it = f c
|
|
||||||
return toOption it
|
|
||||||
}
|
|
||||||
|> withReconn conn
|
|
||||||
|
|
||||||
/// Execute a query that does not return a result, using the above reconnect logic
|
|
||||||
let withReconnIgnore (conn : IConnection) (f : IConnection -> Task<'T>) =
|
|
||||||
fun c -> task {
|
|
||||||
let! _ = f c
|
|
||||||
()
|
|
||||||
}
|
|
||||||
|> withReconn conn
|
|
||||||
|
|
||||||
/// Sanitize user input, and create a "contains" pattern for use with RethinkDB queries
|
|
||||||
let regexContains = System.Text.RegularExpressions.Regex.Escape >> sprintf "(?i)%s"
|
|
||||||
|
|
||||||
open JobsJobsJobs.Domain
|
open JobsJobsJobs.Domain
|
||||||
open JobsJobsJobs.Domain.SharedTypes
|
open JobsJobsJobs.Domain.SharedTypes
|
||||||
|
|
||||||
|
/// Sanitize user input, and create a "contains" pattern for use with RethinkDB queries
|
||||||
|
let regexContains = System.Text.RegularExpressions.Regex.Escape >> sprintf "(?i)%s"
|
||||||
|
|
||||||
/// Profile data access functions
|
/// Profile data access functions
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module Profile =
|
module Profile =
|
||||||
|
|
||||||
|
/// Count the current profiles
|
||||||
let count conn =
|
let count conn =
|
||||||
r.Table(Table.Profile)
|
fromTable Table.Profile
|
||||||
.Count()
|
|> count
|
||||||
.RunResultAsync<int64>
|
|> result<int64> conn
|
||||||
|> withReconn conn
|
|
||||||
|
|
||||||
/// Find a profile by citizen ID
|
/// Find a profile by citizen ID
|
||||||
let findById (citizenId : CitizenId) conn =
|
let findById (citizenId : CitizenId) conn =
|
||||||
r.Table(Table.Profile)
|
fromTable Table.Profile
|
||||||
.Get(citizenId)
|
|> get citizenId
|
||||||
.RunResultAsync<Profile>
|
|> resultOption<Profile> conn
|
||||||
|> withReconnOption conn
|
|
||||||
|
|
||||||
/// Insert or update a profile
|
/// Insert or update a profile
|
||||||
let save (profile : Profile) conn =
|
let save (profile : Profile) conn =
|
||||||
r.Table(Table.Profile)
|
fromTable Table.Profile
|
||||||
.Get(profile.id)
|
|> get profile.id
|
||||||
.Replace(profile)
|
|> replace profile
|
||||||
.RunWriteAsync
|
|> write conn
|
||||||
|> withReconnIgnore conn
|
|
||||||
|
|
||||||
/// Delete a citizen's profile
|
/// Delete a citizen's profile
|
||||||
let delete (citizenId : CitizenId) conn =
|
let delete (citizenId : CitizenId) conn =
|
||||||
r.Table(Table.Profile)
|
fromTable Table.Profile
|
||||||
.Get(citizenId)
|
|> get citizenId
|
||||||
.Delete()
|
|> delete
|
||||||
.RunWriteAsync
|
|> write conn
|
||||||
|> withReconnIgnore conn
|
|
||||||
|
|
||||||
/// Search profiles (logged-on users)
|
/// Search profiles (logged-on users)
|
||||||
let search (srch : ProfileSearch) conn =
|
let search (search : ProfileSearch) conn =
|
||||||
fun c ->
|
(seq<ReqlExpr -> ReqlExpr> {
|
||||||
(seq {
|
match search.continentId with
|
||||||
match srch.continentId with
|
| Some cId -> yield (fun q -> q.Filter (r.HashMap (nameof search.continentId, ContinentId.ofString cId)))
|
||||||
| Some conId ->
|
|
||||||
yield (fun (q : ReqlExpr) ->
|
|
||||||
q.Filter (r.HashMap (nameof srch.continentId, ContinentId.ofString conId)) :> ReqlExpr)
|
|
||||||
| None -> ()
|
| None -> ()
|
||||||
match srch.remoteWork with
|
match search.remoteWork with
|
||||||
| "" -> ()
|
| "" -> ()
|
||||||
| _ -> yield (fun q -> q.Filter (r.HashMap (nameof srch.remoteWork, srch.remoteWork = "yes")) :> ReqlExpr)
|
| _ -> yield (fun q -> q.Filter (r.HashMap (nameof search.remoteWork, search.remoteWork = "yes")))
|
||||||
match srch.skill with
|
match search.skill with
|
||||||
| Some skl ->
|
| Some skl ->
|
||||||
yield (fun q -> q.Filter (ReqlFunction1(fun it ->
|
yield (fun q -> q.Filter (ReqlFunction1(fun it ->
|
||||||
upcast it.G("skills").Contains (ReqlFunction1(fun s ->
|
it.G("skills").Contains (ReqlFunction1(fun s -> s.G("description").Match (regexContains skl))))))
|
||||||
upcast s.G("description").Match (regexContains skl))))) :> ReqlExpr)
|
|
||||||
| None -> ()
|
| None -> ()
|
||||||
match srch.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 (fun q -> q.Filter (ReqlFunction1(fun it ->
|
||||||
upcast it.G("biography").Match(txt).Or (it.G("experience").Match txt))) :> ReqlExpr)
|
it.G("biography").Match(txt).Or (it.G("experience").Match txt))))
|
||||||
| None -> ()
|
| None -> ()
|
||||||
}
|
}
|
||||||
|> Seq.toList
|
|> Seq.toList
|
||||||
|
@ -291,40 +235,33 @@ module Profile =
|
||||||
.EqJoin("id", r.Table Table.Citizen)
|
.EqJoin("id", r.Table Table.Citizen)
|
||||||
.Without(r.HashMap ("right", "id"))
|
.Without(r.HashMap ("right", "id"))
|
||||||
.Zip () :> ReqlExpr))
|
.Zip () :> ReqlExpr))
|
||||||
.Merge(ReqlFunction1 (fun it ->
|
|> mergeFunc (fun it ->
|
||||||
upcast r
|
r.HashMap("displayName",
|
||||||
.HashMap("displayName",
|
|
||||||
r.Branch (it.G("realName" ).Default_("").Ne "", it.G "realName",
|
r.Branch (it.G("realName" ).Default_("").Ne "", it.G "realName",
|
||||||
it.G("displayName").Default_("").Ne "", it.G "displayName",
|
it.G("displayName").Default_("").Ne "", it.G "displayName",
|
||||||
it.G "mastodonUser"))
|
it.G "mastodonUser"))
|
||||||
.With ("citizenId", it.G "id")))
|
.With ("citizenId", it.G "id"))
|
||||||
.Pluck("citizenId", "displayName", "seekingEmployment", "remoteWork", "fullTime", "lastUpdatedOn")
|
|> pluck [ "citizenId"; "displayName"; "seekingEmployment"; "remoteWork"; "fullTime"; "lastUpdatedOn" ]
|
||||||
.OrderBy(ReqlFunction1 (fun it -> upcast it.G("displayName").Downcase ()))
|
|> orderByFunc (fun it -> it.G("displayName").Downcase ())
|
||||||
.RunResultAsync<ProfileSearchResult list> c
|
|> result<ProfileSearchResult list> conn
|
||||||
|> withReconn conn
|
|
||||||
|
|
||||||
// Search profiles (public)
|
// Search profiles (public)
|
||||||
let publicSearch (srch : PublicSearch) conn =
|
let publicSearch (search : PublicSearch) conn =
|
||||||
fun c ->
|
(seq<ReqlExpr -> ReqlExpr> {
|
||||||
(seq {
|
match search.continentId with
|
||||||
match srch.continentId with
|
| Some cId -> yield (fun q -> q.Filter (r.HashMap (nameof search.continentId, ContinentId.ofString cId)))
|
||||||
| Some conId ->
|
|
||||||
yield (fun (q : ReqlExpr) ->
|
|
||||||
q.Filter (r.HashMap (nameof srch.continentId, ContinentId.ofString conId)) :> ReqlExpr)
|
|
||||||
| None -> ()
|
| None -> ()
|
||||||
match srch.region with
|
match search.region with
|
||||||
| Some reg ->
|
| Some reg ->
|
||||||
yield (fun q ->
|
yield (fun q -> q.Filter (ReqlFunction1 (fun it -> upcast it.G("region").Match (regexContains reg))))
|
||||||
q.Filter (ReqlFunction1 (fun it -> upcast it.G("region").Match (regexContains reg))) :> ReqlExpr)
|
|
||||||
| None -> ()
|
| None -> ()
|
||||||
match srch.remoteWork with
|
match search.remoteWork with
|
||||||
| "" -> ()
|
| "" -> ()
|
||||||
| _ -> yield (fun q -> q.Filter (r.HashMap (nameof srch.remoteWork, srch.remoteWork = "yes")) :> ReqlExpr)
|
| _ -> yield (fun q -> q.Filter (r.HashMap (nameof search.remoteWork, search.remoteWork = "yes")))
|
||||||
match srch.skill with
|
match search.skill with
|
||||||
| Some skl ->
|
| Some skl ->
|
||||||
yield (fun q -> q.Filter (ReqlFunction1 (fun it ->
|
yield (fun q -> q.Filter (ReqlFunction1 (fun it ->
|
||||||
upcast it.G("skills").Contains (ReqlFunction1(fun s ->
|
it.G("skills").Contains (ReqlFunction1(fun s -> s.G("description").Match (regexContains skl))))))
|
||||||
upcast s.G("description").Match (regexContains skl))))) :> ReqlExpr)
|
|
||||||
| None -> ()
|
| None -> ()
|
||||||
}
|
}
|
||||||
|> Seq.toList
|
|> Seq.toList
|
||||||
|
@ -334,17 +271,15 @@ module Profile =
|
||||||
.EqJoin("continentId", r.Table Table.Continent)
|
.EqJoin("continentId", r.Table Table.Continent)
|
||||||
.Without(r.HashMap ("right", "id"))
|
.Without(r.HashMap ("right", "id"))
|
||||||
.Zip()
|
.Zip()
|
||||||
.Filter(r.HashMap ("isPublic", true)) :> ReqlExpr))
|
.Filter(r.HashMap ("isPublic", true))))
|
||||||
.Merge(ReqlFunction1 (fun it ->
|
|> mergeFunc (fun it ->
|
||||||
upcast r
|
r.HashMap("skills",
|
||||||
.HashMap("skills",
|
|
||||||
it.G("skills").Map (ReqlFunction1 (fun skill ->
|
it.G("skills").Map (ReqlFunction1 (fun skill ->
|
||||||
upcast 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")))
|
.With("continent", it.G "name"))
|
||||||
.Pluck("continent", "region", "skills", "remoteWork")
|
|> pluck [ "continent"; "region"; "skills"; "remoteWork" ]
|
||||||
.RunResultAsync<PublicSearchResult list> c
|
|> result<PublicSearchResult list> conn
|
||||||
|> withReconn conn
|
|
||||||
|
|
||||||
/// Citizen data access functions
|
/// Citizen data access functions
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
|
@ -352,68 +287,57 @@ module Citizen =
|
||||||
|
|
||||||
/// Find a citizen by their ID
|
/// Find a citizen by their ID
|
||||||
let findById (citizenId : CitizenId) conn =
|
let findById (citizenId : CitizenId) conn =
|
||||||
r.Table(Table.Citizen)
|
fromTable Table.Citizen
|
||||||
.Get(citizenId)
|
|> get citizenId
|
||||||
.RunResultAsync<Citizen>
|
|> resultOption<Citizen> conn
|
||||||
|> withReconnOption conn
|
|
||||||
|
|
||||||
/// Find a citizen by their Mastodon username
|
/// Find a citizen by their Mastodon username
|
||||||
let findByMastodonUser (instance : string) (mastodonUser : string) conn =
|
let findByMastodonUser (instance : string) (mastodonUser : string) conn = task {
|
||||||
fun c -> task {
|
|
||||||
let! u =
|
let! u =
|
||||||
r.Table(Table.Citizen)
|
fromTable Table.Citizen
|
||||||
.GetAll(r.Array (instance, mastodonUser)).OptArg("index", "instanceUser").Limit(1)
|
|> getAllWithIndex [ r.Array (instance, mastodonUser) ] "instanceUser"
|
||||||
.RunResultAsync<Citizen list> c
|
|> limit 1
|
||||||
return u |> List.tryHead
|
|> result<Citizen list> conn
|
||||||
|
return List.tryHead u
|
||||||
}
|
}
|
||||||
|> withReconn conn
|
|
||||||
|
|
||||||
/// Add a citizen
|
/// Add a citizen
|
||||||
let add (citizen : Citizen) conn =
|
let add (citizen : Citizen) conn =
|
||||||
r.Table(Table.Citizen)
|
fromTable Table.Citizen
|
||||||
.Insert(citizen)
|
|> insert citizen
|
||||||
.RunWriteAsync
|
|> write conn
|
||||||
|> withReconnIgnore conn
|
|
||||||
|
|
||||||
/// Update the display name and last seen on date for a citizen
|
/// Update the display name and last seen on date for a citizen
|
||||||
let logOnUpdate (citizen : Citizen) conn =
|
let logOnUpdate (citizen : Citizen) conn =
|
||||||
r.Table(Table.Citizen)
|
fromTable Table.Citizen
|
||||||
.Get(citizen.id)
|
|> get citizen.id
|
||||||
.Update(r.HashMap( nameof citizen.displayName, citizen.displayName)
|
|> update (r.HashMap( nameof citizen.displayName, citizen.displayName)
|
||||||
.With (nameof citizen.lastSeenOn, citizen.lastSeenOn))
|
.With (nameof citizen.lastSeenOn, citizen.lastSeenOn))
|
||||||
.RunWriteAsync
|
|> write conn
|
||||||
|> withReconnIgnore conn
|
|
||||||
|
|
||||||
/// Delete a citizen
|
/// Delete a citizen
|
||||||
let delete citizenId conn =
|
let delete citizenId conn = task {
|
||||||
fun c -> task {
|
do! Profile.delete citizenId conn
|
||||||
do! Profile.delete citizenId c
|
do! fromTable Table.Success
|
||||||
let! _ =
|
|> getAllWithIndex [ citizenId ] "citizenId"
|
||||||
r.Table(Table.Success)
|
|> delete
|
||||||
.GetAll(citizenId).OptArg("index", "citizenId")
|
|> write conn
|
||||||
.Delete()
|
do! fromTable Table.Listing
|
||||||
.RunWriteAsync c
|
|> getAllWithIndex [ citizenId ] "citizenId"
|
||||||
let! _ =
|
|> delete
|
||||||
r.Table(Table.Listing)
|
|> write conn
|
||||||
.GetAll(citizenId).OptArg("index", "citizenId")
|
do! fromTable Table.Citizen
|
||||||
.Delete()
|
|> get citizenId
|
||||||
.RunWriteAsync c
|
|> delete
|
||||||
let! _ =
|
|> write conn
|
||||||
r.Table(Table.Citizen)
|
|
||||||
.Get(citizenId)
|
|
||||||
.Delete()
|
|
||||||
.RunWriteAsync c
|
|
||||||
()
|
|
||||||
}
|
}
|
||||||
|> withReconnIgnore conn
|
|
||||||
|
|
||||||
/// Update a citizen's real name
|
/// Update a citizen's real name
|
||||||
let realNameUpdate (citizenId : CitizenId) (realName : string option) conn =
|
let realNameUpdate (citizenId : CitizenId) (realName : string option) conn =
|
||||||
r.Table(Table.Citizen)
|
fromTable Table.Citizen
|
||||||
.Get(citizenId)
|
|> get citizenId
|
||||||
.Update(r.HashMap (nameof realName, realName))
|
|> update (r.HashMap (nameof realName, realName))
|
||||||
.RunWriteAsync
|
|> write conn
|
||||||
|> withReconnIgnore conn
|
|
||||||
|
|
||||||
|
|
||||||
/// Continent data access functions
|
/// Continent data access functions
|
||||||
|
@ -422,16 +346,14 @@ module Continent =
|
||||||
|
|
||||||
/// Get all continents
|
/// Get all continents
|
||||||
let all conn =
|
let all conn =
|
||||||
r.Table(Table.Continent)
|
fromTable Table.Continent
|
||||||
.RunResultAsync<Continent list>
|
|> result<Continent list> conn
|
||||||
|> withReconn conn
|
|
||||||
|
|
||||||
/// Get a continent by its ID
|
/// Get a continent by its ID
|
||||||
let findById (contId : ContinentId) conn =
|
let findById (contId : ContinentId) conn =
|
||||||
r.Table(Table.Continent)
|
fromTable Table.Continent
|
||||||
.Get(contId)
|
|> get contId
|
||||||
.RunResultAsync<Continent>
|
|> resultOption<Continent> conn
|
||||||
|> withReconnOption conn
|
|
||||||
|
|
||||||
|
|
||||||
/// Job listing data access functions
|
/// Job listing data access functions
|
||||||
|
@ -442,91 +364,77 @@ module Listing =
|
||||||
|
|
||||||
/// 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 =
|
||||||
r.Table(Table.Listing)
|
fromTable Table.Listing
|
||||||
.GetAll(citizenId).OptArg("index", nameof citizenId)
|
|> getAllWithIndex [ citizenId ] (nameof citizenId)
|
||||||
.EqJoin("continentId", r.Table Table.Continent)
|
|> eqJoin "continentId" (fromTable Table.Continent)
|
||||||
.Map(ReqlFunction1 (fun it -> upcast r.HashMap("listing", it.G "left").With ("continent", it.G "right")))
|
|> mapFunc (fun it -> r.HashMap("listing", it.G "left").With ("continent", it.G "right"))
|
||||||
.RunResultAsync<ListingForView list>
|
|> result<ListingForView list> conn
|
||||||
|> withReconn conn
|
|
||||||
|
|
||||||
/// Find a listing by its ID
|
/// Find a listing by its ID
|
||||||
let findById (listingId : ListingId) conn =
|
let findById (listingId : ListingId) conn =
|
||||||
r.Table(Table.Listing)
|
fromTable Table.Listing
|
||||||
.Get(listingId)
|
|> get listingId
|
||||||
.RunResultAsync<Listing>
|
|> resultOption<Listing> conn
|
||||||
|> withReconnOption conn
|
|
||||||
|
|
||||||
/// Find a listing by its ID for viewing (includes continent information)
|
/// Find a listing by its ID for viewing (includes continent information)
|
||||||
let findByIdForView (listingId : ListingId) conn =
|
let findByIdForView (listingId : ListingId) conn = task {
|
||||||
fun c -> task {
|
|
||||||
let! listing =
|
let! listing =
|
||||||
r.Table(Table.Listing)
|
fromTable Table.Listing
|
||||||
.Filter(r.HashMap ("id", listingId))
|
|> filter (r.HashMap ("id", listingId))
|
||||||
.EqJoin("continentId", r.Table Table.Continent)
|
|> eqJoin "continentId" (fromTable Table.Continent)
|
||||||
.Map(ReqlFunction1 (fun it -> upcast r.HashMap("listing", it.G "left").With ("continent", it.G "right")))
|
|> mapFunc (fun it -> r.HashMap("listing", it.G "left").With ("continent", it.G "right"))
|
||||||
.RunResultAsync<ListingForView list> c
|
|> result<ListingForView list> conn
|
||||||
return List.tryHead listing
|
return List.tryHead listing
|
||||||
}
|
}
|
||||||
|> withReconn conn
|
|
||||||
|
|
||||||
/// Add a listing
|
/// Add a listing
|
||||||
let add (listing : Listing) conn =
|
let add (listing : Listing) conn =
|
||||||
r.Table(Table.Listing)
|
fromTable Table.Listing
|
||||||
.Insert(listing)
|
|> insert listing
|
||||||
.RunWriteAsync
|
|> write conn
|
||||||
|> withReconnIgnore conn
|
|
||||||
|
|
||||||
/// Update a listing
|
/// Update a listing
|
||||||
let update (listing : Listing) conn =
|
let update (listing : Listing) conn =
|
||||||
r.Table(Table.Listing)
|
fromTable Table.Listing
|
||||||
.Get(listing.id)
|
|> get listing.id
|
||||||
.Replace(listing)
|
|> replace listing
|
||||||
.RunWriteAsync
|
|> write conn
|
||||||
|> withReconnIgnore conn
|
|
||||||
|
|
||||||
/// Expire a listing
|
/// Expire a listing
|
||||||
let expire (listingId : ListingId) (fromHere : bool) (now : Instant) conn =
|
let expire (listingId : ListingId) (fromHere : bool) (now : Instant) conn =
|
||||||
r.Table(Table.Listing)
|
(fromTable Table.Listing
|
||||||
.Get(listingId)
|
|> get listingId)
|
||||||
.Update (r.HashMap("isExpired", true).With("wasFilledHere", fromHere).With ("updatedOn", now))
|
.Update (r.HashMap("isExpired", true).With("wasFilledHere", fromHere).With ("updatedOn", now))
|
||||||
.RunWriteAsync
|
|> write conn
|
||||||
|> withReconnIgnore conn
|
|
||||||
|
|
||||||
/// Search job listings
|
/// Search job listings
|
||||||
let search (srch : ListingSearch) conn =
|
let search (search : ListingSearch) conn =
|
||||||
fun c ->
|
(seq<ReqlExpr -> ReqlExpr> {
|
||||||
(seq {
|
match search.continentId with
|
||||||
match srch.continentId with
|
| Some cId -> yield (fun q -> q.Filter (r.HashMap (nameof search.continentId, ContinentId.ofString cId)))
|
||||||
| Some conId ->
|
|
||||||
yield (fun (q : ReqlExpr) ->
|
|
||||||
q.Filter (r.HashMap (nameof srch.continentId, ContinentId.ofString conId)) :> ReqlExpr)
|
|
||||||
| None -> ()
|
| None -> ()
|
||||||
match srch.region with
|
match search.region with
|
||||||
| Some rgn ->
|
| Some rgn ->
|
||||||
yield (fun q ->
|
yield (fun q ->
|
||||||
q.Filter (ReqlFunction1 (fun it ->
|
q.Filter (ReqlFunction1 (fun it -> it.G(nameof search.region).Match (regexContains rgn))))
|
||||||
upcast it.G(nameof srch.region).Match (regexContains rgn))) :> ReqlExpr)
|
|
||||||
| None -> ()
|
| None -> ()
|
||||||
match srch.remoteWork with
|
match search.remoteWork with
|
||||||
| "" -> ()
|
| "" -> ()
|
||||||
| _ ->
|
| _ -> yield (fun q -> q.Filter (r.HashMap (nameof search.remoteWork, search.remoteWork = "yes")))
|
||||||
yield (fun q -> q.Filter (r.HashMap (nameof srch.remoteWork, srch.remoteWork = "yes")) :> ReqlExpr)
|
match search.text with
|
||||||
match srch.text with
|
|
||||||
| Some text ->
|
| Some text ->
|
||||||
yield (fun q ->
|
yield (fun q ->
|
||||||
q.Filter (ReqlFunction1 (fun it ->
|
q.Filter (ReqlFunction1 (fun it -> it.G(nameof search.text).Match (regexContains text))))
|
||||||
upcast it.G(nameof srch.text).Match (regexContains text))) :> ReqlExpr)
|
|
||||||
| None -> ()
|
| None -> ()
|
||||||
}
|
}
|
||||||
|> Seq.toList
|
|> Seq.toList
|
||||||
|> List.fold
|
|> List.fold
|
||||||
(fun q f -> f q)
|
(fun q f -> f q)
|
||||||
(r.Table(Table.Listing)
|
(fromTable Table.Listing
|
||||||
.GetAll(false).OptArg ("index", "isExpired") :> ReqlExpr))
|
|> getAllWithIndex [ false ] "isExpired" :> ReqlExpr))
|
||||||
.EqJoin("continentId", r.Table Table.Continent)
|
|> eqJoin "continentId" (fromTable Table.Continent)
|
||||||
.Map(ReqlFunction1 (fun it -> upcast r.HashMap("listing", it.G "left").With ("continent", it.G "right")))
|
|> mapFunc (fun it -> r.HashMap("listing", it.G "left").With ("continent", it.G "right"))
|
||||||
.RunResultAsync<ListingForView list> c
|
|> result<ListingForView list> conn
|
||||||
|> withReconn conn
|
|
||||||
|
|
||||||
|
|
||||||
/// Success story data access functions
|
/// Success story data access functions
|
||||||
|
@ -535,33 +443,29 @@ module Success =
|
||||||
|
|
||||||
/// Find a success report by its ID
|
/// Find a success report by its ID
|
||||||
let findById (successId : SuccessId) conn =
|
let findById (successId : SuccessId) conn =
|
||||||
r.Table(Table.Success)
|
fromTable Table.Success
|
||||||
.Get(successId)
|
|> get successId
|
||||||
.RunResultAsync<Success>
|
|> resultOption conn
|
||||||
|> withReconnOption conn
|
|
||||||
|
|
||||||
/// Insert or update a success story
|
/// Insert or update a success story
|
||||||
let save (success : Success) conn =
|
let save (success : Success) conn =
|
||||||
r.Table(Table.Success)
|
fromTable Table.Success
|
||||||
.Get(success.id)
|
|> get success.id
|
||||||
.Replace(success)
|
|> replace success
|
||||||
.RunWriteAsync
|
|> write conn
|
||||||
|> withReconnIgnore conn
|
|
||||||
|
|
||||||
// Retrieve all success stories
|
// Retrieve all success stories
|
||||||
let all conn =
|
let all conn =
|
||||||
r.Table(Table.Success)
|
(fromTable Table.Success
|
||||||
.EqJoin("citizenId", r.Table Table.Citizen)
|
|> eqJoin "citizenId" (fromTable Table.Citizen))
|
||||||
.Without(r.HashMap ("right", "id"))
|
.Without(r.HashMap ("right", "id"))
|
||||||
.Zip()
|
|> zip
|
||||||
.Merge(ReqlFunction1 (fun it ->
|
|> mergeFunc (fun it ->
|
||||||
upcast r
|
r.HashMap("citizenName",
|
||||||
.HashMap("citizenName",
|
|
||||||
r.Branch(it.G("realName" ).Default_("").Ne "", it.G "realName",
|
r.Branch(it.G("realName" ).Default_("").Ne "", it.G "realName",
|
||||||
it.G("displayName").Default_("").Ne "", it.G "displayName",
|
it.G("displayName").Default_("").Ne "", it.G "displayName",
|
||||||
it.G "mastodonUser"))
|
it.G "mastodonUser"))
|
||||||
.With ("hasStory", it.G("story").Default_("").Gt "")))
|
.With ("hasStory", it.G("story").Default_("").Gt ""))
|
||||||
.Pluck("id", "citizenId", "citizenName", "recordedOn", "fromHere", "hasStory")
|
|> pluck [ "id"; "citizenId"; "citizenName"; "recordedOn"; "fromHere"; "hasStory" ]
|
||||||
.OrderBy(r.Desc "recordedOn")
|
|> orderByDescending "recordedOn"
|
||||||
.RunResultAsync<StoryEntry list>
|
|> result<StoryEntry list> conn
|
||||||
|> withReconn conn
|
|
||||||
|
|
|
@ -21,14 +21,13 @@ module Error =
|
||||||
open System.Threading.Tasks
|
open System.Threading.Tasks
|
||||||
|
|
||||||
/// URL prefixes for the Vue app
|
/// URL prefixes for the Vue app
|
||||||
let vueUrls = [
|
let vueUrls =
|
||||||
"/how-it-works"; "/privacy-policy"; "/terms-of-service"; "/citizen"; "/help-wanted"; "/listing"; "/profile"
|
[ "/how-it-works"; "/privacy-policy"; "/terms-of-service"; "/citizen"; "/help-wanted"; "/listing"; "/profile"
|
||||||
"/so-long"; "/success-story"
|
"/so-long"; "/success-story"
|
||||||
]
|
]
|
||||||
|
|
||||||
/// Handler that will return a status code 404 and the text "Not Found"
|
/// Handler that will return a status code 404 and the text "Not Found"
|
||||||
let notFound : HttpHandler =
|
let notFound : HttpHandler = fun next ctx -> task {
|
||||||
fun next ctx -> task {
|
|
||||||
let fac = ctx.GetService<ILoggerFactory> ()
|
let fac = ctx.GetService<ILoggerFactory> ()
|
||||||
let log = fac.CreateLogger "Handler"
|
let log = fac.CreateLogger "Handler"
|
||||||
let path = string ctx.Request.Path
|
let path = string ctx.Request.Path
|
||||||
|
@ -107,8 +106,7 @@ module Helpers =
|
||||||
module Citizen =
|
module Citizen =
|
||||||
|
|
||||||
// GET: /api/citizen/log-on/[code]
|
// GET: /api/citizen/log-on/[code]
|
||||||
let logOn (abbr, authCode) : HttpHandler =
|
let logOn (abbr, authCode) : HttpHandler = fun next ctx -> task {
|
||||||
fun next ctx -> task {
|
|
||||||
// Step 1 - Verify with Mastodon
|
// Step 1 - Verify with Mastodon
|
||||||
let cfg = authConfig ctx
|
let cfg = authConfig ctx
|
||||||
|
|
||||||
|
@ -154,18 +152,14 @@ module Citizen =
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET: /api/citizen/[id]
|
// GET: /api/citizen/[id]
|
||||||
let get citizenId : HttpHandler =
|
let get citizenId : HttpHandler = authorize >=> fun next ctx -> task {
|
||||||
authorize
|
|
||||||
>=> fun next ctx -> task {
|
|
||||||
match! Data.Citizen.findById (CitizenId citizenId) (conn ctx) with
|
match! Data.Citizen.findById (CitizenId citizenId) (conn ctx) with
|
||||||
| Some citizen -> return! json citizen next ctx
|
| Some citizen -> return! json citizen next ctx
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// DELETE: /api/citizen
|
// DELETE: /api/citizen
|
||||||
let delete : HttpHandler =
|
let delete : HttpHandler = authorize >=> fun next ctx -> task {
|
||||||
authorize
|
|
||||||
>=> fun next ctx -> task {
|
|
||||||
do! Data.Citizen.delete (currentCitizenId ctx) (conn ctx)
|
do! Data.Citizen.delete (currentCitizenId ctx) (conn ctx)
|
||||||
return! ok next ctx
|
return! ok next ctx
|
||||||
}
|
}
|
||||||
|
@ -176,8 +170,7 @@ module Citizen =
|
||||||
module Continent =
|
module Continent =
|
||||||
|
|
||||||
// GET: /api/continent/all
|
// GET: /api/continent/all
|
||||||
let all : HttpHandler =
|
let all : HttpHandler = fun next ctx -> task {
|
||||||
fun next ctx -> task {
|
|
||||||
let! continents = Data.Continent.all (conn ctx)
|
let! continents = Data.Continent.all (conn ctx)
|
||||||
return! json continents next ctx
|
return! json continents next ctx
|
||||||
}
|
}
|
||||||
|
@ -187,17 +180,18 @@ module Continent =
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module Instances =
|
module Instances =
|
||||||
|
|
||||||
/// Convert a Masotodon instance to the one we use in the API
|
/// Convert a Mastodon instance to the one we use in the API
|
||||||
let private toInstance (inst : MastodonInstance) =
|
let private toInstance (inst : MastodonInstance) =
|
||||||
{ name = inst.Name
|
{ name = inst.Name
|
||||||
url = inst.Url
|
url = inst.Url
|
||||||
abbr = inst.Abbr
|
abbr = inst.Abbr
|
||||||
clientId = inst.ClientId
|
clientId = inst.ClientId
|
||||||
|
isEnabled = inst.IsEnabled
|
||||||
|
reason = inst.Reason
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET: /api/instances
|
// GET: /api/instances
|
||||||
let all : HttpHandler =
|
let all : HttpHandler = fun next ctx -> task {
|
||||||
fun next ctx -> task {
|
|
||||||
return! json ((authConfig ctx).Instances |> Array.map toInstance) next ctx
|
return! json ((authConfig ctx).Instances |> Array.map toInstance) next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,35 +207,27 @@ module Listing =
|
||||||
let private parseDate = DateTime.Parse >> LocalDate.FromDateTime
|
let private parseDate = DateTime.Parse >> LocalDate.FromDateTime
|
||||||
|
|
||||||
// GET: /api/listings/mine
|
// GET: /api/listings/mine
|
||||||
let mine : HttpHandler =
|
let mine : HttpHandler = authorize >=> fun next ctx -> task {
|
||||||
authorize
|
|
||||||
>=> fun next ctx -> task {
|
|
||||||
let! listings = Data.Listing.findByCitizen (currentCitizenId ctx) (conn ctx)
|
let! listings = Data.Listing.findByCitizen (currentCitizenId ctx) (conn ctx)
|
||||||
return! json listings next ctx
|
return! json listings next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET: /api/listing/[id]
|
// GET: /api/listing/[id]
|
||||||
let get listingId : HttpHandler =
|
let get listingId : HttpHandler = authorize >=> fun next ctx -> task {
|
||||||
authorize
|
|
||||||
>=> fun next ctx -> task {
|
|
||||||
match! Data.Listing.findById (ListingId listingId) (conn ctx) with
|
match! Data.Listing.findById (ListingId listingId) (conn ctx) with
|
||||||
| Some listing -> return! json listing next ctx
|
| Some listing -> return! json listing next ctx
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET: /api/listing/view/[id]
|
// GET: /api/listing/view/[id]
|
||||||
let view listingId : HttpHandler =
|
let view listingId : HttpHandler = authorize >=> fun next ctx -> task {
|
||||||
authorize
|
|
||||||
>=> fun next ctx -> task {
|
|
||||||
match! Data.Listing.findByIdForView (ListingId listingId) (conn ctx) with
|
match! Data.Listing.findByIdForView (ListingId listingId) (conn ctx) with
|
||||||
| Some listing -> return! json listing next ctx
|
| Some listing -> return! json listing next ctx
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST: /listings
|
// POST: /listings
|
||||||
let add : HttpHandler =
|
let add : HttpHandler = authorize >=> fun next ctx -> task {
|
||||||
authorize
|
|
||||||
>=> fun next ctx -> task {
|
|
||||||
let! form = ctx.BindJsonAsync<ListingForm> ()
|
let! form = ctx.BindJsonAsync<ListingForm> ()
|
||||||
let now = (clock ctx).GetCurrentInstant ()
|
let now = (clock ctx).GetCurrentInstant ()
|
||||||
do! Data.Listing.add
|
do! Data.Listing.add
|
||||||
|
@ -262,9 +248,7 @@ module Listing =
|
||||||
}
|
}
|
||||||
|
|
||||||
// PUT: /api/listing/[id]
|
// PUT: /api/listing/[id]
|
||||||
let update listingId : HttpHandler =
|
let update listingId : HttpHandler = authorize >=> fun next ctx -> task {
|
||||||
authorize
|
|
||||||
>=> fun next ctx -> task {
|
|
||||||
let dbConn = conn ctx
|
let dbConn = conn ctx
|
||||||
match! Data.Listing.findById (ListingId listingId) dbConn with
|
match! Data.Listing.findById (ListingId listingId) dbConn with
|
||||||
| Some listing when listing.citizenId <> (currentCitizenId ctx) -> return! Error.notAuthorized next ctx
|
| Some listing when listing.citizenId <> (currentCitizenId ctx) -> return! Error.notAuthorized next ctx
|
||||||
|
@ -285,9 +269,7 @@ module Listing =
|
||||||
}
|
}
|
||||||
|
|
||||||
// PATCH: /api/listing/[id]
|
// PATCH: /api/listing/[id]
|
||||||
let expire listingId : HttpHandler =
|
let expire listingId : HttpHandler = authorize >=> fun next ctx -> task {
|
||||||
authorize
|
|
||||||
>=> fun next ctx -> FSharp.Control.Tasks.Affine.task {
|
|
||||||
let dbConn = conn ctx
|
let dbConn = conn ctx
|
||||||
let now = clock(ctx).GetCurrentInstant ()
|
let now = clock(ctx).GetCurrentInstant ()
|
||||||
match! Data.Listing.findById (ListingId listingId) dbConn with
|
match! Data.Listing.findById (ListingId listingId) dbConn with
|
||||||
|
@ -311,9 +293,7 @@ module Listing =
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET: /api/listing/search
|
// GET: /api/listing/search
|
||||||
let search : HttpHandler =
|
let search : HttpHandler = authorize >=> fun next ctx -> task {
|
||||||
authorize
|
|
||||||
>=> fun next ctx -> task {
|
|
||||||
let search = ctx.BindQueryString<ListingSearch> ()
|
let search = ctx.BindQueryString<ListingSearch> ()
|
||||||
let! results = Data.Listing.search search (conn ctx)
|
let! results = Data.Listing.search search (conn ctx)
|
||||||
return! json results next ctx
|
return! json results next ctx
|
||||||
|
@ -327,27 +307,21 @@ module Profile =
|
||||||
// GET: /api/profile
|
// GET: /api/profile
|
||||||
// This returns the current citizen's profile, or a 204 if it is not found (a citizen not having a profile yet
|
// This returns the current citizen's profile, or a 204 if it is not found (a citizen not having a profile yet
|
||||||
// is not an error). The "get" handler returns a 404 if a profile is not found.
|
// is not an error). The "get" handler returns a 404 if a profile is not found.
|
||||||
let current : HttpHandler =
|
let current : HttpHandler = authorize >=> fun next ctx -> task {
|
||||||
authorize
|
|
||||||
>=> fun next ctx -> task {
|
|
||||||
match! Data.Profile.findById (currentCitizenId ctx) (conn ctx) with
|
match! Data.Profile.findById (currentCitizenId ctx) (conn ctx) with
|
||||||
| Some profile -> return! json profile next ctx
|
| Some profile -> return! json profile next ctx
|
||||||
| None -> return! Successful.NO_CONTENT next ctx
|
| None -> return! Successful.NO_CONTENT next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET: /api/profile/get/[id]
|
// GET: /api/profile/get/[id]
|
||||||
let get citizenId : HttpHandler =
|
let get citizenId : HttpHandler = authorize >=> fun next ctx -> task {
|
||||||
authorize
|
|
||||||
>=> fun next ctx -> task {
|
|
||||||
match! Data.Profile.findById (CitizenId citizenId) (conn ctx) with
|
match! Data.Profile.findById (CitizenId citizenId) (conn ctx) with
|
||||||
| Some profile -> return! json profile next ctx
|
| Some profile -> return! json profile next ctx
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET: /api/profile/view/[id]
|
// GET: /api/profile/view/[id]
|
||||||
let view citizenId : HttpHandler =
|
let view citizenId : HttpHandler = authorize >=> fun next ctx -> task {
|
||||||
authorize
|
|
||||||
>=> fun next ctx -> task {
|
|
||||||
let citId = CitizenId citizenId
|
let citId = CitizenId citizenId
|
||||||
let dbConn = conn ctx
|
let dbConn = conn ctx
|
||||||
match! Data.Profile.findById citId dbConn with
|
match! Data.Profile.findById citId dbConn with
|
||||||
|
@ -357,8 +331,8 @@ module Profile =
|
||||||
match! Data.Continent.findById profile.continentId dbConn with
|
match! Data.Continent.findById profile.continentId dbConn with
|
||||||
| Some continent ->
|
| Some continent ->
|
||||||
return!
|
return!
|
||||||
json {
|
json
|
||||||
profile = profile
|
{ profile = profile
|
||||||
citizen = citizen
|
citizen = citizen
|
||||||
continent = continent
|
continent = continent
|
||||||
} next ctx
|
} next ctx
|
||||||
|
@ -368,17 +342,13 @@ module Profile =
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET: /api/profile/count
|
// GET: /api/profile/count
|
||||||
let count : HttpHandler =
|
let count : HttpHandler = authorize >=> fun next ctx -> task {
|
||||||
authorize
|
|
||||||
>=> fun next ctx -> task {
|
|
||||||
let! theCount = Data.Profile.count (conn ctx)
|
let! theCount = Data.Profile.count (conn ctx)
|
||||||
return! json { count = theCount } next ctx
|
return! json { count = theCount } next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST: /api/profile/save
|
// POST: /api/profile/save
|
||||||
let save : HttpHandler =
|
let save : HttpHandler = authorize >=> fun next ctx -> task {
|
||||||
authorize
|
|
||||||
>=> fun next ctx -> task {
|
|
||||||
let citizenId = currentCitizenId ctx
|
let citizenId = currentCitizenId ctx
|
||||||
let dbConn = conn ctx
|
let dbConn = conn ctx
|
||||||
let! form = ctx.BindJsonAsync<ProfileForm>()
|
let! form = ctx.BindJsonAsync<ProfileForm>()
|
||||||
|
@ -412,9 +382,7 @@ module Profile =
|
||||||
}
|
}
|
||||||
|
|
||||||
// PATCH: /api/profile/employment-found
|
// PATCH: /api/profile/employment-found
|
||||||
let employmentFound : HttpHandler =
|
let employmentFound : HttpHandler = authorize >=> fun next ctx -> task {
|
||||||
authorize
|
|
||||||
>=> fun next ctx -> task {
|
|
||||||
let dbConn = conn ctx
|
let dbConn = conn ctx
|
||||||
match! Data.Profile.findById (currentCitizenId ctx) dbConn with
|
match! Data.Profile.findById (currentCitizenId ctx) dbConn with
|
||||||
| Some profile ->
|
| Some profile ->
|
||||||
|
@ -424,25 +392,20 @@ module Profile =
|
||||||
}
|
}
|
||||||
|
|
||||||
// DELETE: /api/profile
|
// DELETE: /api/profile
|
||||||
let delete : HttpHandler =
|
let delete : HttpHandler = authorize >=> fun next ctx -> task {
|
||||||
authorize
|
|
||||||
>=> fun next ctx -> task {
|
|
||||||
do! Data.Profile.delete (currentCitizenId ctx) (conn ctx)
|
do! Data.Profile.delete (currentCitizenId ctx) (conn ctx)
|
||||||
return! ok next ctx
|
return! ok next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET: /api/profile/search
|
// GET: /api/profile/search
|
||||||
let search : HttpHandler =
|
let search : HttpHandler = authorize >=> fun next ctx -> task {
|
||||||
authorize
|
|
||||||
>=> fun next ctx -> task {
|
|
||||||
let search = ctx.BindQueryString<ProfileSearch> ()
|
let search = ctx.BindQueryString<ProfileSearch> ()
|
||||||
let! results = Data.Profile.search search (conn ctx)
|
let! results = Data.Profile.search search (conn ctx)
|
||||||
return! json results next ctx
|
return! json results next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET: /api/profile/public-search
|
// GET: /api/profile/public-search
|
||||||
let publicSearch : HttpHandler =
|
let publicSearch : HttpHandler = fun next ctx -> task {
|
||||||
fun next ctx -> task {
|
|
||||||
let search = ctx.BindQueryString<PublicSearch> ()
|
let search = ctx.BindQueryString<PublicSearch> ()
|
||||||
let! results = Data.Profile.publicSearch search (conn ctx)
|
let! results = Data.Profile.publicSearch search (conn ctx)
|
||||||
return! json results next ctx
|
return! json results next ctx
|
||||||
|
@ -456,26 +419,20 @@ module Success =
|
||||||
open System
|
open System
|
||||||
|
|
||||||
// GET: /api/success/[id]
|
// GET: /api/success/[id]
|
||||||
let get successId : HttpHandler =
|
let get successId : HttpHandler = authorize >=> fun next ctx -> task {
|
||||||
authorize
|
|
||||||
>=> fun next ctx -> task {
|
|
||||||
match! Data.Success.findById (SuccessId successId) (conn ctx) with
|
match! Data.Success.findById (SuccessId successId) (conn ctx) with
|
||||||
| Some story -> return! json story next ctx
|
| Some story -> return! json story next ctx
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET: /api/success/list
|
// GET: /api/success/list
|
||||||
let all : HttpHandler =
|
let all : HttpHandler = authorize >=> fun next ctx -> task {
|
||||||
authorize
|
|
||||||
>=> fun next ctx -> task {
|
|
||||||
let! stories = Data.Success.all (conn ctx)
|
let! stories = Data.Success.all (conn ctx)
|
||||||
return! json stories next ctx
|
return! json stories next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST: /api/success/save
|
// POST: /api/success/save
|
||||||
let save : HttpHandler =
|
let save : HttpHandler = authorize >=> fun next ctx -> task {
|
||||||
authorize
|
|
||||||
>=> fun next ctx -> task {
|
|
||||||
let citizenId = currentCitizenId ctx
|
let citizenId = currentCitizenId ctx
|
||||||
let dbConn = conn ctx
|
let dbConn = conn ctx
|
||||||
let now = (clock ctx).GetCurrentInstant ()
|
let now = (clock ctx).GetCurrentInstant ()
|
||||||
|
@ -528,15 +485,9 @@ let allEndpoints = [
|
||||||
routef "/%O/view" Listing.view
|
routef "/%O/view" Listing.view
|
||||||
route "s/mine" Listing.mine
|
route "s/mine" Listing.mine
|
||||||
]
|
]
|
||||||
PATCH [
|
PATCH [ routef "/%O" Listing.expire ]
|
||||||
routef "/%O" Listing.expire
|
POST [ route "s" Listing.add ]
|
||||||
]
|
PUT [ routef "/%O" Listing.update ]
|
||||||
POST [
|
|
||||||
route "s" Listing.add
|
|
||||||
]
|
|
||||||
PUT [
|
|
||||||
routef "/%O" Listing.update
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
subRoute "/profile" [
|
subRoute "/profile" [
|
||||||
GET_HEAD [
|
GET_HEAD [
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<PublishSingleFile>true</PublishSingleFile>
|
||||||
|
<SelfContained>false</SelfContained>
|
||||||
<WarnOn>3390;$(WarnOn)</WarnOn>
|
<WarnOn>3390;$(WarnOn)</WarnOn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
@ -22,13 +23,14 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Giraffe" Version="5.0.0" />
|
<PackageReference Include="Giraffe" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.8" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.6" />
|
||||||
<PackageReference Include="Microsoft.FSharpLu.Json" Version="0.11.7" />
|
<PackageReference Include="Microsoft.FSharpLu.Json" Version="0.11.7" />
|
||||||
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.0.0" />
|
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.0.0" />
|
||||||
<PackageReference Include="Polly" Version="7.2.2" />
|
|
||||||
<PackageReference Include="RethinkDb.Driver" Version="2.3.150" />
|
<PackageReference Include="RethinkDb.Driver" Version="2.3.150" />
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.11.1" />
|
<PackageReference Include="RethinkDb.Driver.FSharp" Version="0.9.0-beta-05" />
|
||||||
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.21.0" />
|
||||||
|
<PackageReference Update="FSharp.Core" Version="6.0.5" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -5,17 +5,23 @@
|
||||||
"0": {
|
"0": {
|
||||||
"Name": "No Agenda Social",
|
"Name": "No Agenda Social",
|
||||||
"Url": "https://noagendasocial.com",
|
"Url": "https://noagendasocial.com",
|
||||||
"Abbr": "nas"
|
"Abbr": "nas",
|
||||||
|
"IsEnabled": true,
|
||||||
|
"Reason": ""
|
||||||
},
|
},
|
||||||
"1": {
|
"1": {
|
||||||
"Name": "ITM Slaves!",
|
"Name": "ITM Slaves!",
|
||||||
"Url": "https://itmslaves.com",
|
"Url": "https://itmslaves.com",
|
||||||
"Abbr": "itm"
|
"Abbr": "itm",
|
||||||
|
"IsEnabled": false,
|
||||||
|
"Reason": "This site has changed platforms, and its integration is not yet restored"
|
||||||
},
|
},
|
||||||
"2": {
|
"2": {
|
||||||
"Name": "Liberty Woof",
|
"Name": "Liberty Woof",
|
||||||
"Url": "https://libertywoof.com",
|
"Url": "https://libertywoof.com",
|
||||||
"Abbr": "lw"
|
"Abbr": "lw",
|
||||||
|
"IsEnabled": false,
|
||||||
|
"Reason": "This site may have gone away; it is currently inaccessible"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user