Help wanted #23
|
@ -176,8 +176,13 @@ module Startup =
|
||||||
/// Determine if a record type (not nullable) is null
|
/// Determine if a record type (not nullable) is null
|
||||||
let toOption x = match x |> box |> isNull with true -> None | false -> Some x
|
let toOption x = match x |> box |> isNull with true -> None | false -> Some x
|
||||||
|
|
||||||
/// A retry policy where we will reconnect to RethinkDB if it has gone away
|
[<AutoOpen>]
|
||||||
let withReconn (conn : IConnection) =
|
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
|
Policy
|
||||||
.Handle<ReqlDriverError>()
|
.Handle<ReqlDriverError>()
|
||||||
.RetryAsync(System.Action<exn, int> (fun ex _ ->
|
.RetryAsync(System.Action<exn, int> (fun ex _ ->
|
||||||
|
@ -185,13 +190,28 @@ let withReconn (conn : IConnection) =
|
||||||
match ex.Message.Contains "socket" with
|
match ex.Message.Contains "socket" with
|
||||||
| true ->
|
| true ->
|
||||||
printf "Reconnecting to RethinkDB"
|
printf "Reconnecting to RethinkDB"
|
||||||
(conn :?> Connection).Reconnect()
|
(conn :?> Connection).Reconnect false
|
||||||
| 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
|
/// Sanitize user input, and create a "contains" pattern for use with RethinkDB queries
|
||||||
let regexContains (it : string) =
|
let regexContains = System.Text.RegularExpressions.Regex.Escape >> sprintf "(?i)%s"
|
||||||
System.Text.RegularExpressions.Regex.Escape it
|
|
||||||
|> sprintf "(?i)%s"
|
|
||||||
|
|
||||||
open JobsJobsJobs.Domain
|
open JobsJobsJobs.Domain
|
||||||
open JobsJobsJobs.Domain.SharedTypes
|
open JobsJobsJobs.Domain.SharedTypes
|
||||||
|
@ -202,46 +222,37 @@ open RethinkDb.Driver.Ast
|
||||||
module Profile =
|
module Profile =
|
||||||
|
|
||||||
let count conn =
|
let count conn =
|
||||||
withReconn(conn).ExecuteAsync(fun () ->
|
|
||||||
r.Table(Table.Profile)
|
r.Table(Table.Profile)
|
||||||
.Count()
|
.Count()
|
||||||
.RunResultAsync<int64> conn)
|
.RunResultAsync<int64>
|
||||||
|
|> withReconn conn
|
||||||
|
|
||||||
/// Find a profile by citizen ID
|
/// Find a profile by citizen ID
|
||||||
let findById (citizenId : CitizenId) conn =
|
let findById (citizenId : CitizenId) conn =
|
||||||
withReconn(conn).ExecuteAsync(fun () -> task {
|
|
||||||
let! profile =
|
|
||||||
r.Table(Table.Profile)
|
r.Table(Table.Profile)
|
||||||
.Get(citizenId)
|
.Get(citizenId)
|
||||||
.RunResultAsync<Profile> conn
|
.RunResultAsync<Profile>
|
||||||
return toOption profile
|
|> withReconnOption conn
|
||||||
})
|
|
||||||
|
|
||||||
/// Insert or update a profile
|
/// Insert or update a profile
|
||||||
let save (profile : Profile) conn =
|
let save (profile : Profile) conn =
|
||||||
withReconn(conn).ExecuteAsync(fun () -> task {
|
|
||||||
let! _ =
|
|
||||||
r.Table(Table.Profile)
|
r.Table(Table.Profile)
|
||||||
.Get(profile.id)
|
.Get(profile.id)
|
||||||
.Replace(profile)
|
.Replace(profile)
|
||||||
.RunWriteAsync conn
|
.RunWriteAsync
|
||||||
()
|
|> withReconnIgnore conn
|
||||||
})
|
|
||||||
|
|
||||||
/// Delete a citizen's profile
|
/// Delete a citizen's profile
|
||||||
let delete (citizenId : CitizenId) conn =
|
let delete (citizenId : CitizenId) conn =
|
||||||
withReconn(conn).ExecuteAsync(fun () -> task {
|
|
||||||
let! _ =
|
|
||||||
r.Table(Table.Profile)
|
r.Table(Table.Profile)
|
||||||
.Get(citizenId)
|
.Get(citizenId)
|
||||||
.Delete()
|
.Delete()
|
||||||
.RunWriteAsync conn
|
.RunWriteAsync
|
||||||
()
|
|> withReconnIgnore conn
|
||||||
})
|
|
||||||
|
|
||||||
/// Search profiles (logged-on users)
|
/// Search profiles (logged-on users)
|
||||||
let search (srch : ProfileSearch) conn =
|
let search (srch : ProfileSearch) conn =
|
||||||
withReconn(conn).ExecuteAsync(fun () ->
|
fun c ->
|
||||||
(seq {
|
(seq {
|
||||||
match srch.continentId with
|
match srch.continentId with
|
||||||
| Some conId ->
|
| Some conId ->
|
||||||
|
@ -261,29 +272,31 @@ module Profile =
|
||||||
| 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)
|
upcast it.G("biography").Match(txt).Or (it.G("experience").Match txt))) :> ReqlExpr)
|
||||||
| None -> ()
|
| None -> ()
|
||||||
}
|
}
|
||||||
|> Seq.toList
|
|> Seq.toList
|
||||||
|> List.fold
|
|> List.fold
|
||||||
(fun q f -> f q)
|
(fun q f -> f q)
|
||||||
(r.Table(Table.Profile)
|
(r.Table(Table.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 ->
|
.Merge(ReqlFunction1 (fun it ->
|
||||||
upcast r
|
upcast 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("naUser")))
|
it.G "naUser"))
|
||||||
.With("citizenId", it.G("id"))))
|
.With ("citizenId", it.G "id")))
|
||||||
.Pluck("citizenId", "displayName", "seekingEmployment", "remoteWork", "fullTime", "lastUpdatedOn")
|
.Pluck("citizenId", "displayName", "seekingEmployment", "remoteWork", "fullTime", "lastUpdatedOn")
|
||||||
.RunResultAsync<ProfileSearchResult list> conn)
|
.OrderBy(ReqlFunction1 (fun it -> upcast it.G("displayName").Downcase ()))
|
||||||
|
.RunResultAsync<ProfileSearchResult list> c
|
||||||
|
|> withReconn conn
|
||||||
|
|
||||||
// Search profiles (public)
|
// Search profiles (public)
|
||||||
let publicSearch (srch : PublicSearch) conn =
|
let publicSearch (srch : PublicSearch) conn =
|
||||||
withReconn(conn).ExecuteAsync(fun () ->
|
fun c ->
|
||||||
(seq {
|
(seq {
|
||||||
match srch.continentId with
|
match srch.continentId with
|
||||||
| Some conId ->
|
| Some conId ->
|
||||||
|
@ -309,7 +322,7 @@ module Profile =
|
||||||
|> List.fold
|
|> List.fold
|
||||||
(fun q f -> f q)
|
(fun q f -> f q)
|
||||||
(r.Table(Table.Profile)
|
(r.Table(Table.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)) :> ReqlExpr))
|
||||||
|
@ -317,12 +330,12 @@ module Profile =
|
||||||
upcast r
|
upcast 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"),
|
upcast 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> conn)
|
.RunResultAsync<PublicSearchResult list> c
|
||||||
|
|> withReconn conn
|
||||||
|
|
||||||
/// Citizen data access functions
|
/// Citizen data access functions
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
|
@ -330,78 +343,64 @@ module Citizen =
|
||||||
|
|
||||||
/// Find a citizen by their ID
|
/// Find a citizen by their ID
|
||||||
let findById (citizenId : CitizenId) conn =
|
let findById (citizenId : CitizenId) conn =
|
||||||
withReconn(conn).ExecuteAsync(fun () -> task {
|
|
||||||
let! citizen =
|
|
||||||
r.Table(Table.Citizen)
|
r.Table(Table.Citizen)
|
||||||
.Get(citizenId)
|
.Get(citizenId)
|
||||||
.RunResultAsync<Citizen> conn
|
.RunResultAsync<Citizen>
|
||||||
return toOption citizen
|
|> withReconnOption conn
|
||||||
})
|
|
||||||
|
|
||||||
/// Find a citizen by their No Agenda Social username
|
/// Find a citizen by their No Agenda Social username
|
||||||
let findByNaUser (naUser : string) conn =
|
let findByNaUser (naUser : string) conn =
|
||||||
withReconn(conn).ExecuteAsync(fun () -> task {
|
|
||||||
let! citizen =
|
|
||||||
r.Table(Table.Citizen)
|
r.Table(Table.Citizen)
|
||||||
.GetAll(naUser).OptArg("index", "naUser").Nth(0)
|
.GetAll(naUser).OptArg("index", "naUser").Nth(0)
|
||||||
.RunResultAsync<Citizen> conn
|
.RunResultAsync<Citizen>
|
||||||
return toOption citizen
|
|> withReconnOption conn
|
||||||
})
|
|
||||||
|
|
||||||
/// Add a citizen
|
/// Add a citizen
|
||||||
let add (citizen : Citizen) conn =
|
let add (citizen : Citizen) conn =
|
||||||
withReconn(conn).ExecuteAsync(fun () -> task {
|
|
||||||
let! _ =
|
|
||||||
r.Table(Table.Citizen)
|
r.Table(Table.Citizen)
|
||||||
.Insert(citizen)
|
.Insert(citizen)
|
||||||
.RunWriteAsync conn
|
.RunWriteAsync
|
||||||
()
|
|> 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 =
|
||||||
withReconn(conn).ExecuteAsync(fun () -> task {
|
|
||||||
let! _ =
|
|
||||||
r.Table(Table.Citizen)
|
r.Table(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 conn
|
.RunWriteAsync
|
||||||
()
|
|> withReconnIgnore conn
|
||||||
})
|
|
||||||
|
|
||||||
/// Delete a citizen
|
/// Delete a citizen
|
||||||
let delete citizenId conn =
|
let delete citizenId conn =
|
||||||
withReconn(conn).ExecuteAsync(fun () -> task {
|
fun c -> task {
|
||||||
do! Profile.delete citizenId conn
|
do! Profile.delete citizenId c
|
||||||
let! _ =
|
let! _ =
|
||||||
r.Table(Table.Success)
|
r.Table(Table.Success)
|
||||||
.GetAll(citizenId).OptArg("index", "citizenId")
|
.GetAll(citizenId).OptArg("index", "citizenId")
|
||||||
.Delete()
|
.Delete()
|
||||||
.RunWriteAsync conn
|
.RunWriteAsync c
|
||||||
let! _ =
|
let! _ =
|
||||||
r.Table(Table.Listing)
|
r.Table(Table.Listing)
|
||||||
.GetAll(citizenId).OptArg("index", "citizenId")
|
.GetAll(citizenId).OptArg("index", "citizenId")
|
||||||
.Delete()
|
.Delete()
|
||||||
.RunWriteAsync conn
|
.RunWriteAsync c
|
||||||
let! _ =
|
let! _ =
|
||||||
r.Table(Table.Citizen)
|
r.Table(Table.Citizen)
|
||||||
.Get(citizenId)
|
.Get(citizenId)
|
||||||
.Delete()
|
.Delete()
|
||||||
.RunWriteAsync conn
|
.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 =
|
||||||
withReconn(conn).ExecuteAsync(fun () -> task {
|
|
||||||
let! _ =
|
|
||||||
r.Table(Table.Citizen)
|
r.Table(Table.Citizen)
|
||||||
.Get(citizenId)
|
.Get(citizenId)
|
||||||
.Update(r.HashMap (nameof realName, realName))
|
.Update(r.HashMap (nameof realName, realName))
|
||||||
.RunWriteAsync conn
|
.RunWriteAsync
|
||||||
()
|
|> withReconnIgnore conn
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
/// Continent data access functions
|
/// Continent data access functions
|
||||||
|
@ -410,19 +409,16 @@ module Continent =
|
||||||
|
|
||||||
/// Get all continents
|
/// Get all continents
|
||||||
let all conn =
|
let all conn =
|
||||||
withReconn(conn).ExecuteAsync(fun () ->
|
|
||||||
r.Table(Table.Continent)
|
r.Table(Table.Continent)
|
||||||
.RunResultAsync<Continent list> conn)
|
.RunResultAsync<Continent list>
|
||||||
|
|> withReconn conn
|
||||||
|
|
||||||
/// Get a continent by its ID
|
/// Get a continent by its ID
|
||||||
let findById (contId : ContinentId) conn =
|
let findById (contId : ContinentId) conn =
|
||||||
withReconn(conn).ExecuteAsync(fun () -> task {
|
|
||||||
let! continent =
|
|
||||||
r.Table(Table.Continent)
|
r.Table(Table.Continent)
|
||||||
.Get(contId)
|
.Get(contId)
|
||||||
.RunResultAsync<Continent> conn
|
.RunResultAsync<Continent>
|
||||||
return toOption continent
|
|> withReconnOption conn
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
/// Job listing data access functions
|
/// Job listing data access functions
|
||||||
|
@ -433,70 +429,59 @@ 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 =
|
||||||
withReconn(conn).ExecuteAsync(fun () ->
|
|
||||||
r.Table(Table.Listing)
|
r.Table(Table.Listing)
|
||||||
.GetAll(citizenId).OptArg("index", nameof citizenId)
|
.GetAll(citizenId).OptArg("index", nameof citizenId)
|
||||||
.EqJoin("continentId", r.Table(Table.Continent))
|
.EqJoin("continentId", r.Table Table.Continent)
|
||||||
.Map(ReqlFunction1(fun it -> upcast r.HashMap("listing", it.G("left")).With("continent", it.G("right"))))
|
.Map(ReqlFunction1 (fun it -> upcast r.HashMap("listing", it.G "left").With ("continent", it.G "right")))
|
||||||
.RunResultAsync<ListingForView list> conn)
|
.RunResultAsync<ListingForView list>
|
||||||
|
|> withReconn conn
|
||||||
|
|
||||||
/// Find a listing by its ID
|
/// Find a listing by its ID
|
||||||
let findById (listingId : ListingId) conn =
|
let findById (listingId : ListingId) conn =
|
||||||
withReconn(conn).ExecuteAsync(fun () -> task {
|
|
||||||
let! listing =
|
|
||||||
r.Table(Table.Listing)
|
r.Table(Table.Listing)
|
||||||
.Get(listingId)
|
.Get(listingId)
|
||||||
.RunResultAsync<Listing> conn
|
.RunResultAsync<Listing>
|
||||||
return toOption listing
|
|> 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 =
|
||||||
withReconn(conn).ExecuteAsync(fun () -> task {
|
fun c -> task {
|
||||||
let! listing =
|
let! listing =
|
||||||
r.Table(Table.Listing)
|
r.Table(Table.Listing)
|
||||||
.Filter(r.HashMap ("id", listingId))
|
.Filter(r.HashMap ("id", listingId))
|
||||||
.EqJoin("continentId", r.Table(Table.Continent))
|
.EqJoin("continentId", r.Table Table.Continent)
|
||||||
.Map(ReqlFunction1(fun it -> upcast r.HashMap("listing", it.G("left")).With("continent", it.G("right"))))
|
.Map(ReqlFunction1 (fun it -> upcast r.HashMap("listing", it.G "left").With ("continent", it.G "right")))
|
||||||
.RunResultAsync<ListingForView list> conn
|
.RunResultAsync<ListingForView list> c
|
||||||
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 =
|
||||||
withReconn(conn).ExecuteAsync(fun () -> task {
|
|
||||||
let! _ =
|
|
||||||
r.Table(Table.Listing)
|
r.Table(Table.Listing)
|
||||||
.Insert(listing)
|
.Insert(listing)
|
||||||
.RunWriteAsync conn
|
.RunWriteAsync
|
||||||
()
|
|> withReconnIgnore conn
|
||||||
})
|
|
||||||
|
|
||||||
/// Update a listing
|
/// Update a listing
|
||||||
let update (listing : Listing) conn =
|
let update (listing : Listing) conn =
|
||||||
withReconn(conn).ExecuteAsync(fun () -> task {
|
|
||||||
let! _ =
|
|
||||||
r.Table(Table.Listing)
|
r.Table(Table.Listing)
|
||||||
.Get(listing.id)
|
.Get(listing.id)
|
||||||
.Replace(listing)
|
.Replace(listing)
|
||||||
.RunWriteAsync conn
|
.RunWriteAsync
|
||||||
()
|
|> 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 =
|
||||||
withReconn(conn).ExecuteAsync(fun () -> task {
|
|
||||||
let! _ =
|
|
||||||
r.Table(Table.Listing)
|
r.Table(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 conn
|
.RunWriteAsync
|
||||||
()
|
|> withReconnIgnore conn
|
||||||
})
|
|
||||||
|
|
||||||
/// Search job listings
|
/// Search job listings
|
||||||
let search (srch : ListingSearch) conn =
|
let search (srch : ListingSearch) conn =
|
||||||
withReconn(conn).ExecuteAsync(fun () ->
|
fun c ->
|
||||||
(seq {
|
(seq {
|
||||||
match srch.continentId with
|
match srch.continentId with
|
||||||
| Some conId ->
|
| Some conId ->
|
||||||
|
@ -525,9 +510,10 @@ module Listing =
|
||||||
(fun q f -> f q)
|
(fun q f -> f q)
|
||||||
(r.Table(Table.Listing)
|
(r.Table(Table.Listing)
|
||||||
.GetAll(false).OptArg ("index", "isExpired") :> ReqlExpr))
|
.GetAll(false).OptArg ("index", "isExpired") :> ReqlExpr))
|
||||||
.EqJoin("continentId", r.Table(Table.Continent))
|
.EqJoin("continentId", r.Table Table.Continent)
|
||||||
.Map(ReqlFunction1(fun it -> upcast r.HashMap("listing", it.G("left")).With("continent", it.G("right"))))
|
.Map(ReqlFunction1 (fun it -> upcast r.HashMap("listing", it.G "left").With ("continent", it.G "right")))
|
||||||
.RunResultAsync<ListingForView list> conn)
|
.RunResultAsync<ListingForView list> c
|
||||||
|
|> withReconn conn
|
||||||
|
|
||||||
|
|
||||||
/// Success story data access functions
|
/// Success story data access functions
|
||||||
|
@ -536,39 +522,33 @@ 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 =
|
||||||
withReconn(conn).ExecuteAsync(fun () -> task {
|
|
||||||
let! success =
|
|
||||||
r.Table(Table.Success)
|
r.Table(Table.Success)
|
||||||
.Get(successId)
|
.Get(successId)
|
||||||
.RunResultAsync<Success> conn
|
.RunResultAsync<Success>
|
||||||
return toOption success
|
|> withReconnOption conn
|
||||||
})
|
|
||||||
|
|
||||||
/// Insert or update a success story
|
/// Insert or update a success story
|
||||||
let save (success : Success) conn =
|
let save (success : Success) conn =
|
||||||
withReconn(conn).ExecuteAsync(fun () -> task {
|
|
||||||
let! _ =
|
|
||||||
r.Table(Table.Success)
|
r.Table(Table.Success)
|
||||||
.Get(success.id)
|
.Get(success.id)
|
||||||
.Replace(success)
|
.Replace(success)
|
||||||
.RunWriteAsync conn
|
.RunWriteAsync
|
||||||
()
|
|> withReconnIgnore conn
|
||||||
})
|
|
||||||
|
|
||||||
// Retrieve all success stories
|
// Retrieve all success stories
|
||||||
let all conn =
|
let all conn =
|
||||||
withReconn(conn).ExecuteAsync(fun () ->
|
|
||||||
r.Table(Table.Success)
|
r.Table(Table.Success)
|
||||||
.EqJoin("citizenId", r.Table(Table.Citizen))
|
.EqJoin("citizenId", r.Table Table.Citizen)
|
||||||
.Without(r.HashMap ("right", "id"))
|
.Without(r.HashMap ("right", "id"))
|
||||||
.Zip()
|
.Zip()
|
||||||
.Merge(ReqlFunction1 (fun it ->
|
.Merge(ReqlFunction1 (fun it ->
|
||||||
upcast r
|
upcast 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("naUser")))
|
it.G "naUser"))
|
||||||
.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"))
|
.OrderBy(r.Desc "recordedOn")
|
||||||
.RunResultAsync<StoryEntry list> conn)
|
.RunResultAsync<StoryEntry list>
|
||||||
|
|> withReconn conn
|
||||||
|
|
|
@ -4,12 +4,12 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "vue-cli-service serve",
|
||||||
"build": "vue-cli-service build --mode development",
|
"build": "vue-cli-service build",
|
||||||
"lint": "vue-cli-service lint",
|
"lint": "vue-cli-service lint",
|
||||||
"apiserve": "vue-cli-service build --mode development && cd ../Api && dotnet run -c Debug"
|
"apiserve": "vue-cli-service build && cd ../Api && dotnet run -c Debug"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mdi/font": "5.9.55",
|
"@mdi/js": "^5.9.55",
|
||||||
"@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",
|
||||||
"bootstrap": "^5.1.0",
|
"bootstrap": "^5.1.0",
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
import { defineComponent } from "vue"
|
import { defineComponent } from "vue"
|
||||||
|
|
||||||
import "bootstrap/dist/css/bootstrap.min.css"
|
import "bootstrap/dist/css/bootstrap.min.css"
|
||||||
import "@mdi/font/css/materialdesignicons.css"
|
|
||||||
|
|
||||||
import { Citizen } from "./api"
|
import { Citizen } from "./api"
|
||||||
import AppFooter from "./components/layout/AppFooter.vue"
|
import AppFooter from "./components/layout/AppFooter.vue"
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
span(:class="iconClass")
|
svg(viewbox="0 0 24 24"): path(:fill="color || 'white'" :d="icon")
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
color?: string
|
||||||
icon: string
|
icon: string
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
/** The CSS class to display the requested icon */
|
|
||||||
const iconClass = `mdi mdi-${props.icon}`
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="sass" scoped>
|
||||||
|
svg
|
||||||
|
width: 24px
|
||||||
|
height: 24px
|
||||||
|
</style>
|
||||||
|
|
|
@ -4,25 +4,37 @@ aside.collapse.show.p-3
|
||||||
p
|
p
|
||||||
nav
|
nav
|
||||||
template(v-if="isLoggedOn")
|
template(v-if="isLoggedOn")
|
||||||
router-link(to="/citizen/dashboard") #[icon(icon="view-dashboard-variant")] Dashboard
|
router-link(to="/citizen/dashboard") #[icon(:icon="mdiViewDashboardVariant")] Dashboard
|
||||||
router-link(to="/help-wanted") #[icon(icon="newspaper-variant-multiple-outline")] Help Wanted!
|
router-link(to="/help-wanted") #[icon(:icon="mdiNewspaperVariantMultipleOutline")] Help Wanted!
|
||||||
router-link(to="/profile/search") #[icon(icon="view-list-outline")] Employment Profiles
|
router-link(to="/profile/search") #[icon(:icon="mdiViewListOutline")] Employment Profiles
|
||||||
router-link(to="/success-story/list") #[icon(icon="thumb-up")] Success Stories
|
router-link(to="/success-story/list") #[icon(:icon="mdiThumbUp")] Success Stories
|
||||||
.separator
|
.separator
|
||||||
router-link(to="/listings/mine") #[icon(icon="sign-text")] My Job Listings
|
router-link(to="/listings/mine") #[icon(:icon="mdiSignText")] My Job Listings
|
||||||
router-link(to="/citizen/profile") #[icon(icon="pencil")] My Employment Profile
|
router-link(to="/citizen/profile") #[icon(:icon="mdiPencil")] My Employment Profile
|
||||||
.separator
|
.separator
|
||||||
router-link(to="/citizen/log-off") #[icon(icon="logout-variant")] Log Off
|
router-link(to="/citizen/log-off") #[icon(:icon="mdiLogoutVariant")] Log Off
|
||||||
template(v-else)
|
template(v-else)
|
||||||
router-link(to="/") #[icon(icon="home")] Home
|
router-link(to="/") #[icon(:icon="mdiHome")] Home
|
||||||
router-link(to="/profile/seeking") #[icon(icon="view-list-outline")] Job Seekers
|
router-link(to="/profile/seeking") #[icon(:icon="mdiViewListOutline")] Job Seekers
|
||||||
router-link(to="/citizen/log-on") #[icon(icon="login-variant")] Log On
|
router-link(to="/citizen/log-on") #[icon(:icon="mdiLoginVariant")] Log On
|
||||||
router-link(to="/how-it-works") #[icon(icon="help-circle-outline")] How It Works
|
router-link(to="/how-it-works") #[icon(:icon="mdiHelpCircleOutline")] How It Works
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from "vue"
|
import { computed } from "vue"
|
||||||
import { useStore } from "@/store"
|
import { useStore } from "@/store"
|
||||||
|
import {
|
||||||
|
mdiHelpCircleOutline,
|
||||||
|
mdiHome,
|
||||||
|
mdiLoginVariant,
|
||||||
|
mdiLogoutVariant,
|
||||||
|
mdiNewspaperVariantMultipleOutline,
|
||||||
|
mdiPencil,
|
||||||
|
mdiSignText,
|
||||||
|
mdiThumbUp,
|
||||||
|
mdiViewDashboardVariant,
|
||||||
|
mdiViewListOutline
|
||||||
|
} from "@mdi/js"
|
||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
|
|
||||||
|
@ -40,6 +52,10 @@ aside
|
||||||
min-width: 250px
|
min-width: 250px
|
||||||
position: sticky
|
position: sticky
|
||||||
top: 0
|
top: 0
|
||||||
|
path
|
||||||
|
fill: white
|
||||||
|
path:hover
|
||||||
|
fill: black
|
||||||
a:link, a:visited
|
a:link, a:visited
|
||||||
text-decoration: none
|
text-decoration: none
|
||||||
color: white
|
color: white
|
||||||
|
|
|
@ -6,10 +6,12 @@ article
|
||||||
Welcome to Jobs, Jobs, Jobs (AKA No Agenda Careers), where citizens of Gitmo Nation can assist one another in
|
Welcome to Jobs, Jobs, Jobs (AKA No Agenda Careers), where citizens of Gitmo Nation can assist one another in
|
||||||
finding employment. This will enable them to continue providing value-for-value to Adam and John, as they continue
|
finding employment. This will enable them to continue providing value-for-value to Adam and John, as they continue
|
||||||
their work deconstructing the misinformation that passes for news on a day-to-day basis.
|
their work deconstructing the misinformation that passes for news on a day-to-day basis.
|
||||||
p.
|
p
|
||||||
Do you not understand the terms in the paragraph above? No worries; just head over to
|
| Do you not understand the terms in the paragraph above? No worries; just head over to
|
||||||
#[a(href="https://noagendashow.net" target="_blank") The Best Podcast in the Universe]
|
|
|
||||||
#[= " "]#[em #[audio-clip(clip="thats-true") (that’s true!)]] and find out what you’re missing.
|
a(href="https://noagendashow.net" target="_blank") The Best Podcast in the Universe
|
||||||
|
= " "
|
||||||
|
| #[em #[audio-clip(clip="thats-true") (that’s true!)]] and find out what you’re missing.
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
article
|
article
|
||||||
page-title(title="How It Works")
|
page-title(title="How It Works")
|
||||||
h3 How It Works
|
h3 How It Works
|
||||||
h5.pb-3.text-muted: em Last Updated August 29th, 2021
|
h5.pb-3.text-muted: em Last Updated August 29#[sup th], 2021
|
||||||
p: em.
|
p: em.
|
||||||
Show me how to #[a(href="#listing-search") find a job]
|
Show me how to #[a(href="#listing-search") find a job]
|
||||||
#[!= " • "]#[a(href="#listing") list a job opportunity]
|
#[!= " • "]#[a(href="#listing") list a job opportunity]
|
||||||
|
|
|
@ -37,8 +37,8 @@ article.container
|
||||||
.card-footer: router-link.btn.btn-outline-secondary(to="/profile/search") Search Profiles
|
.card-footer: router-link.btn.btn-outline-secondary(to="/profile/search") Search Profiles
|
||||||
p
|
p
|
||||||
p.
|
p.
|
||||||
To see how this application works, check out “How It Works” in the sidebar (last updated June
|
To see how this application works, check out “How It Works” in the sidebar (last updated August
|
||||||
14#[sup th], 2021).
|
29#[sup th], 2021).
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
|
@ -55,11 +55,11 @@ article
|
||||||
label.form-check-label(for="isPublic") Allow my profile to be searched publicly (outside NA Social)
|
label.form-check-label(for="isPublic") Allow my profile to be searched publicly (outside NA Social)
|
||||||
.col-12
|
.col-12
|
||||||
p.text-danger(v-if="v$.$error") Please correct the errors above
|
p.text-danger(v-if="v$.$error") Please correct the errors above
|
||||||
button.btn.btn-primary(@click.prevent="saveProfile") #[icon(icon="content-save-outline")] Save
|
button.btn.btn-primary(@click.prevent="saveProfile") #[icon(:icon="mdiContentSaveOutline")] Save
|
||||||
template(v-if="!isNew")
|
template(v-if="!isNew")
|
||||||
|
|
|
|
||||||
router-link.btn.btn-outline-secondary(:to="`/profile/${user.citizenId}/view`").
|
router-link.btn.btn-outline-secondary(:to="`/profile/${user.citizenId}/view`").
|
||||||
#[icon(icon="file-account-outline")] View Your User Profile
|
#[icon(color="#6c757d" :icon="mdiFileAccountOutline")] View Your User Profile
|
||||||
hr
|
hr
|
||||||
p.text-muted.fst-italic.
|
p.text-muted.fst-italic.
|
||||||
(If you want to delete your profile, or your entire account,
|
(If you want to delete your profile, or your entire account,
|
||||||
|
@ -69,6 +69,7 @@ article
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, reactive } from "vue"
|
import { computed, ref, reactive } from "vue"
|
||||||
|
import { mdiContentSaveOutline, mdiFileAccountOutline } from "@mdi/js"
|
||||||
import useVuelidate from "@vuelidate/core"
|
import useVuelidate from "@vuelidate/core"
|
||||||
import { required } from "@vuelidate/validators"
|
import { required } from "@vuelidate/validators"
|
||||||
|
|
||||||
|
|
|
@ -31,13 +31,14 @@ article
|
||||||
label(for="neededBy") Needed By
|
label(for="neededBy") Needed By
|
||||||
.col-12
|
.col-12
|
||||||
p.text-danger(v-if="v$.$error") Please correct the errors above
|
p.text-danger(v-if="v$.$error") Please correct the errors above
|
||||||
button.btn.btn-primary(@click.prevent="saveListing(true)") #[icon(icon="content-save-outline")] Save
|
button.btn.btn-primary(@click.prevent="saveListing(true)") #[icon(:icon="mdiContentSaveOutline")] Save
|
||||||
maybe-save(:saveAction="doSave" :validator="v$")
|
maybe-save(:saveAction="doSave" :validator="v$")
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, reactive } from "vue"
|
import { computed, reactive } from "vue"
|
||||||
import { useRoute, useRouter } from "vue-router"
|
import { useRoute, useRouter } from "vue-router"
|
||||||
|
import { mdiContentSaveOutline } from "@mdi/js"
|
||||||
import useVuelidate from "@vuelidate/core"
|
import useVuelidate from "@vuelidate/core"
|
||||||
import { required } from "@vuelidate/validators"
|
import { required } from "@vuelidate/validators"
|
||||||
|
|
||||||
|
|
|
@ -17,13 +17,14 @@ article
|
||||||
markdown-editor(id="successStory" label="Your Success Story" v-model:text="v$.successStory.$model")
|
markdown-editor(id="successStory" label="Your Success Story" v-model:text="v$.successStory.$model")
|
||||||
.col-12
|
.col-12
|
||||||
button.btn.btn-primary(@click.prevent="expireListing").
|
button.btn.btn-primary(@click.prevent="expireListing").
|
||||||
#[icon(icon="text-box-remove-outline")] Expire Listing
|
#[icon(:icon="mdiTextBoxRemoveOutline")] Expire Listing
|
||||||
maybe-save(:saveAction="doSave" :validator="v$")
|
maybe-save(:saveAction="doSave" :validator="v$")
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, reactive, Ref, ref } from "vue"
|
import { computed, reactive, Ref, ref } from "vue"
|
||||||
import { useRoute, useRouter } from "vue-router"
|
import { useRoute, useRouter } from "vue-router"
|
||||||
|
import { mdiTextBoxRemoveOutline } from "@mdi/js"
|
||||||
import useVuelidate from "@vuelidate/core"
|
import useVuelidate from "@vuelidate/core"
|
||||||
|
|
||||||
import api, { Listing, ListingExpireForm, LogOnSuccess } from "@/api"
|
import api, { Listing, ListingExpireForm, LogOnSuccess } from "@/api"
|
||||||
|
|
|
@ -23,12 +23,13 @@ article
|
||||||
template(v-if="user.citizenId === it.citizen.id")
|
template(v-if="user.citizenId === it.citizen.id")
|
||||||
br
|
br
|
||||||
br
|
br
|
||||||
router-link.btn.btn-primary(to="/citizen/profile") #[icon(icon="pencil")] Edit Your Profile
|
router-link.btn.btn-primary(to="/citizen/profile") #[icon(:icon="mdiPencil")] Edit Your Profile
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, Ref } from "vue"
|
import { computed, ref, Ref } from "vue"
|
||||||
import { useRoute } from "vue-router"
|
import { useRoute } from "vue-router"
|
||||||
|
import { mdiPencil } from "@mdi/js"
|
||||||
|
|
||||||
import api, { LogOnSuccess, ProfileForView } from "@/api"
|
import api, { LogOnSuccess, ProfileForView } from "@/api"
|
||||||
import { citizenName } from "@/App.vue"
|
import { citizenName } from "@/App.vue"
|
||||||
|
|
|
@ -13,7 +13,7 @@ article
|
||||||
markdown-editor(id="story" label="The Success Story" v-model:text="v$.story.$model")
|
markdown-editor(id="story" label="The Success Story" v-model:text="v$.story.$model")
|
||||||
.col-12
|
.col-12
|
||||||
button.btn.btn-primary(type="submit" @click.prevent="saveStory(true)").
|
button.btn.btn-primary(type="submit" @click.prevent="saveStory(true)").
|
||||||
#[icon(icon="content-save-outline")] Save
|
#[icon(:icon="mdiContentSaveOutline")] Save
|
||||||
p(v-if="isNew"): em (Saving this will set “Seeking Employment” to “No” on your profile.)
|
p(v-if="isNew"): em (Saving this will set “Seeking Employment” to “No” on your profile.)
|
||||||
maybe-save(:saveAction="doSave" :validator="v$")
|
maybe-save(:saveAction="doSave" :validator="v$")
|
||||||
</template>
|
</template>
|
||||||
|
@ -21,6 +21,7 @@ article
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, reactive } from "vue"
|
import { computed, reactive } from "vue"
|
||||||
import { useRoute, useRouter } from "vue-router"
|
import { useRoute, useRouter } from "vue-router"
|
||||||
|
import { mdiContentSaveOutline } from "@mdi/js"
|
||||||
import useVuelidate from "@vuelidate/core"
|
import useVuelidate from "@vuelidate/core"
|
||||||
|
|
||||||
import api, { LogOnSuccess, StoryForm } from "@/api"
|
import api, { LogOnSuccess, StoryForm } from "@/api"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user