Partially integrate RethinkDB F# driver (#34)
This commit is contained in:
parent
5f9156f6c2
commit
cc21849220
|
@ -63,7 +63,8 @@ Target.create "All" ignore
|
||||||
"BuildClient"
|
"BuildClient"
|
||||||
?=> "BuildServer"
|
?=> "BuildServer"
|
||||||
"BuildClient"
|
"BuildClient"
|
||||||
==> "RunServer"
|
?=> "RunServer"
|
||||||
|
"BuildClient"
|
||||||
==> "BuildAndRun"
|
==> "BuildAndRun"
|
||||||
"BuildClient"
|
"BuildClient"
|
||||||
==> "Publish"
|
==> "Publish"
|
||||||
|
@ -71,4 +72,7 @@ Target.create "All" ignore
|
||||||
"BuildServer"
|
"BuildServer"
|
||||||
==> "All"
|
==> "All"
|
||||||
|
|
||||||
|
"RunServer"
|
||||||
|
==> "BuildAndRun"
|
||||||
|
|
||||||
Target.runOrDefault "All"
|
Target.runOrDefault "All"
|
||||||
|
|
4
src/JobsJobsJobs/App/package-lock.json
generated
4
src/JobsJobsJobs/App/package-lock.json
generated
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "jobs-jobs-jobs",
|
"name": "jobs-jobs-jobs",
|
||||||
"version": "2.2.0",
|
"version": "2.2.2",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "jobs-jobs-jobs",
|
"name": "jobs-jobs-jobs",
|
||||||
"version": "2.2.0",
|
"version": "2.2.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mdi/js": "^5.9.55",
|
"@mdi/js": "^5.9.55",
|
||||||
"@vuelidate/core": "^2.0.0-alpha.24",
|
"@vuelidate/core": "^2.0.0-alpha.24",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"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",
|
||||||
|
|
|
@ -66,7 +66,7 @@ let configureServices (svc : IServiceCollection) =
|
||||||
let log = svcs.GetRequiredService<ILoggerFactory>().CreateLogger "JobsJobsJobs.Api.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 _ =
|
||||||
|
|
|
@ -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 =
|
||||||
|
@ -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,156 +119,93 @@ 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 (search : ProfileSearch) conn =
|
let search (search : ProfileSearch) conn =
|
||||||
|
@ -287,32 +235,30 @@ 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>
|
|> result<ProfileSearchResult list> conn
|
||||||
|> withReconn conn
|
|
||||||
|
|
||||||
// Search profiles (public)
|
// Search profiles (public)
|
||||||
let publicSearch (srch : PublicSearch) conn =
|
let publicSearch (search : PublicSearch) conn =
|
||||||
(seq<ReqlExpr -> ReqlExpr> {
|
(seq<ReqlExpr -> ReqlExpr> {
|
||||||
match srch.continentId with
|
match search.continentId with
|
||||||
| Some cId -> yield (fun q -> q.Filter (r.HashMap (nameof srch.continentId, ContinentId.ofString cId)))
|
| Some cId -> yield (fun q -> q.Filter (r.HashMap (nameof search.continentId, ContinentId.ofString cId)))
|
||||||
| None -> ()
|
| None -> ()
|
||||||
match srch.region with
|
match search.region with
|
||||||
| Some reg ->
|
| Some reg ->
|
||||||
yield (fun q -> q.Filter (ReqlFunction1 (fun it -> upcast it.G("region").Match (regexContains reg))))
|
yield (fun q -> q.Filter (ReqlFunction1 (fun it -> upcast it.G("region").Match (regexContains reg))))
|
||||||
| None -> ()
|
| None -> ()
|
||||||
match srch.remoteWork with
|
match search.remoteWork with
|
||||||
| "" -> ()
|
| "" -> ()
|
||||||
| _ -> yield (fun q -> q.Filter (r.HashMap (nameof srch.remoteWork, srch.remoteWork = "yes")))
|
| _ -> 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 ->
|
||||||
it.G("skills").Contains (ReqlFunction1(fun s -> s.G("description").Match (regexContains skl))))))
|
it.G("skills").Contains (ReqlFunction1(fun s -> s.G("description").Match (regexContains skl))))))
|
||||||
|
@ -326,16 +272,14 @@ module Profile =
|
||||||
.Without(r.HashMap ("right", "id"))
|
.Without(r.HashMap ("right", "id"))
|
||||||
.Zip()
|
.Zip()
|
||||||
.Filter(r.HashMap ("isPublic", true))))
|
.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>
|
|> result<PublicSearchResult list> conn
|
||||||
|> withReconn conn
|
|
||||||
|
|
||||||
/// Citizen data access functions
|
/// Citizen data access functions
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
|
@ -343,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
|
||||||
|
@ -413,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
|
||||||
|
@ -433,55 +364,48 @@ 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 -> 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 -> 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 (search : ListingSearch) conn =
|
let search (search : ListingSearch) conn =
|
||||||
|
@ -506,12 +430,11 @@ module Listing =
|
||||||
|> 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")))
|
|> getAllWithIndex [ false ] "isExpired" :> ReqlExpr))
|
||||||
.EqJoin("continentId", r.Table Table.Continent)
|
|> eqJoin "continentId" (fromTable Table.Continent)
|
||||||
.Map(ReqlFunction1 (fun it -> 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
|
|
||||||
|
|
||||||
|
|
||||||
/// Success story data access functions
|
/// Success story data access functions
|
||||||
|
@ -520,32 +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 ->
|
||||||
r.HashMap("citizenName",
|
r.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
|
|
||||||
|
|
|
@ -23,11 +23,10 @@
|
||||||
</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="RethinkDb.Driver.FSharp" Version="0.9.0-beta-05" />
|
<PackageReference Include="RethinkDb.Driver.FSharp" Version="0.9.0-beta-05" />
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.21.0" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.21.0" />
|
||||||
|
|
Loading…
Reference in New Issue
Block a user