Env swap #21
@ -8,34 +8,12 @@ open Microsoft.Extensions.Hosting
 | 
				
			|||||||
open Giraffe
 | 
					open Giraffe
 | 
				
			||||||
open Giraffe.EndpointRouting
 | 
					open Giraffe.EndpointRouting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// All available routes for the application
 | 
					 | 
				
			||||||
let webApp = [
 | 
					 | 
				
			||||||
  subRoute "/api" [
 | 
					 | 
				
			||||||
    subRoute "/citizen" [
 | 
					 | 
				
			||||||
      GET_HEAD [
 | 
					 | 
				
			||||||
        routef "/log-on/%s" Handlers.Citizen.logOn
 | 
					 | 
				
			||||||
        routef "/get/%O"    Handlers.Citizen.get
 | 
					 | 
				
			||||||
        ]
 | 
					 | 
				
			||||||
      DELETE [ route "" Handlers.Citizen.delete ]
 | 
					 | 
				
			||||||
      ]
 | 
					 | 
				
			||||||
    GET_HEAD [ route "/continent/all" Handlers.Continent.all ]
 | 
					 | 
				
			||||||
    subRoute "/profile" [
 | 
					 | 
				
			||||||
      GET_HEAD [
 | 
					 | 
				
			||||||
        route  ""        Handlers.Profile.current
 | 
					 | 
				
			||||||
        route  "/count"  Handlers.Profile.count
 | 
					 | 
				
			||||||
        routef "/get/%O" Handlers.Profile.get
 | 
					 | 
				
			||||||
        ]
 | 
					 | 
				
			||||||
      PATCH [ route "/employment-found" Handlers.Profile.employmentFound ]
 | 
					 | 
				
			||||||
      POST [ route "/save" Handlers.Profile.save ]
 | 
					 | 
				
			||||||
      ]
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
  ]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// 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
 | 
				
			||||||
    .UseRouting()
 | 
					    .UseRouting()
 | 
				
			||||||
    .UseEndpoints(fun e -> e.MapGiraffeEndpoints webApp)
 | 
					    .UseEndpoints(fun e -> e.MapGiraffeEndpoints Handlers.allEndpoints)
 | 
				
			||||||
  |> ignore
 | 
					  |> ignore
 | 
				
			||||||
 | 
					
 | 
				
			||||||
open NodaTime
 | 
					open NodaTime
 | 
				
			||||||
 | 
				
			|||||||
@ -185,11 +185,15 @@ let withReconn (conn : IConnection) =
 | 
				
			|||||||
            (conn :?> Connection).Reconnect()
 | 
					            (conn :?> Connection).Reconnect()
 | 
				
			||||||
        | false -> ()))
 | 
					        | false -> ()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					open JobsJobsJobs.Domain.SharedTypes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Profile data access functions
 | 
					/// Profile data access functions
 | 
				
			||||||
[<RequireQualifiedAccess>]
 | 
					[<RequireQualifiedAccess>]
 | 
				
			||||||
module Profile =
 | 
					module Profile =
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  open JobsJobsJobs.Domain
 | 
				
			||||||
 | 
					  open RethinkDb.Driver.Ast
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let count conn =
 | 
					  let count conn =
 | 
				
			||||||
    withReconn(conn).ExecuteAsync(fun () ->
 | 
					    withReconn(conn).ExecuteAsync(fun () ->
 | 
				
			||||||
        r.Table(Table.Profile)
 | 
					        r.Table(Table.Profile)
 | 
				
			||||||
@ -227,6 +231,67 @@ module Profile =
 | 
				
			|||||||
            .RunWriteAsync conn
 | 
					            .RunWriteAsync conn
 | 
				
			||||||
        ()
 | 
					        ()
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /// Search profiles (logged-on users)
 | 
				
			||||||
 | 
					  let search (srch : ProfileSearch) conn = task {
 | 
				
			||||||
 | 
					    let results =
 | 
				
			||||||
 | 
					      seq {
 | 
				
			||||||
 | 
					        match srch.continentId with
 | 
				
			||||||
 | 
					        | Some conId ->
 | 
				
			||||||
 | 
					            yield (fun (q : ReqlExpr) ->
 | 
				
			||||||
 | 
					                q.Filter(r.HashMap(nameof srch.continentId, ContinentId.ofString conId)) :> ReqlExpr)
 | 
				
			||||||
 | 
					        | None -> ()
 | 
				
			||||||
 | 
					        match srch.remoteWork with
 | 
				
			||||||
 | 
					        | "" -> ()
 | 
				
			||||||
 | 
					        | _ -> yield (fun q -> q.Filter(r.HashMap(nameof srch.remoteWork, srch.remoteWork = "yes")) :> ReqlExpr)
 | 
				
			||||||
 | 
					        match srch.skill with
 | 
				
			||||||
 | 
					        | Some skl ->
 | 
				
			||||||
 | 
					            yield (fun q -> q.Filter(ReqlFunction1(fun it ->
 | 
				
			||||||
 | 
					                upcast it.G("skills.description").Downcase().Match(skl.ToLowerInvariant ()))) :> ReqlExpr)
 | 
				
			||||||
 | 
					        | None -> ()
 | 
				
			||||||
 | 
					        match srch.bioExperience with
 | 
				
			||||||
 | 
					        | Some text ->
 | 
				
			||||||
 | 
					            let txt = text.ToLowerInvariant ()
 | 
				
			||||||
 | 
					            yield (fun q -> q.Filter(ReqlFunction1(fun it ->
 | 
				
			||||||
 | 
					                upcast it.G("biography" ).Downcase().Match(txt)
 | 
				
			||||||
 | 
					                   .Or(it.G("experience").Downcase().Match(txt)))) :> ReqlExpr)
 | 
				
			||||||
 | 
					        | None -> ()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      |> Seq.toList
 | 
				
			||||||
 | 
					      |> List.fold (fun q f -> f q) (r.Table(Table.Profile) :> ReqlExpr)
 | 
				
			||||||
 | 
					    // TODO: pluck fields, include display name
 | 
				
			||||||
 | 
					    return! results.RunResultAsync<ProfileSearchResult list> conn
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Search profiles (public)
 | 
				
			||||||
 | 
					  let publicSearch (srch : PublicSearch) conn = task {
 | 
				
			||||||
 | 
					    let results =
 | 
				
			||||||
 | 
					      seq {
 | 
				
			||||||
 | 
					        match srch.continentId with
 | 
				
			||||||
 | 
					        | Some conId ->
 | 
				
			||||||
 | 
					            yield (fun (q : ReqlExpr) ->
 | 
				
			||||||
 | 
					                q.Filter(r.HashMap(nameof srch.continentId, ContinentId.ofString conId)) :> ReqlExpr)
 | 
				
			||||||
 | 
					        | None -> ()
 | 
				
			||||||
 | 
					        match srch.region with
 | 
				
			||||||
 | 
					        | Some reg ->
 | 
				
			||||||
 | 
					            yield (fun q ->
 | 
				
			||||||
 | 
					                q.Filter(ReqlFunction1(fun it ->
 | 
				
			||||||
 | 
					                    upcast it.G("region").Downcase().Match(reg.ToLowerInvariant ()))) :> ReqlExpr)
 | 
				
			||||||
 | 
					        | None -> ()
 | 
				
			||||||
 | 
					        match srch.remoteWork with
 | 
				
			||||||
 | 
					        | "" -> ()
 | 
				
			||||||
 | 
					        | _ -> yield (fun q -> q.Filter(r.HashMap(nameof srch.remoteWork, srch.remoteWork = "yes")) :> ReqlExpr)
 | 
				
			||||||
 | 
					        match srch.skill with
 | 
				
			||||||
 | 
					        | Some skl ->
 | 
				
			||||||
 | 
					            yield (fun q -> q.Filter(ReqlFunction1(fun it ->
 | 
				
			||||||
 | 
					                upcast it.G("skills.description").Downcase().Match(skl.ToLowerInvariant ()))) :> ReqlExpr)
 | 
				
			||||||
 | 
					        | None -> ()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      |> Seq.toList
 | 
				
			||||||
 | 
					      |> List.fold (fun q f -> f q) (r.Table(Table.Profile) :> ReqlExpr)
 | 
				
			||||||
 | 
					    // TODO: pluck fields, compile skills
 | 
				
			||||||
 | 
					    return! results.RunResultAsync<PublicSearchResult list> conn
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Citizen data access functions
 | 
					/// Citizen data access functions
 | 
				
			||||||
@ -315,6 +380,18 @@ module Continent =
 | 
				
			|||||||
          .RunResultAsync<Continent list> conn)
 | 
					          .RunResultAsync<Continent list> conn)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Job listing data access functions
 | 
				
			||||||
 | 
					[<RequireQualifiedAccess>]
 | 
				
			||||||
 | 
					module Listing =
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Find all job listings posted by the given citizen
 | 
				
			||||||
 | 
					  let findByCitizen (citizenId : CitizenId) conn =
 | 
				
			||||||
 | 
					    withReconn(conn).ExecuteAsync(fun () ->
 | 
				
			||||||
 | 
					        r.Table(Table.Listing)
 | 
				
			||||||
 | 
					          .GetAll(citizenId).OptArg("index", nameof citizenId)
 | 
				
			||||||
 | 
					          .RunResultAsync<Listing list> conn)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Success story data access functions
 | 
					/// Success story data access functions
 | 
				
			||||||
[<RequireQualifiedAccess>]
 | 
					[<RequireQualifiedAccess>]
 | 
				
			||||||
module Success =
 | 
					module Success =
 | 
				
			||||||
@ -339,3 +416,10 @@ module Success =
 | 
				
			|||||||
            .RunWriteAsync conn)
 | 
					            .RunWriteAsync conn)
 | 
				
			||||||
    ()
 | 
					    ()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Retrieve all success stories  
 | 
				
			||||||
 | 
					  let all conn =
 | 
				
			||||||
 | 
					    // TODO: identify query and fields that will make StoryEntry meaningful
 | 
				
			||||||
 | 
					    withReconn(conn).ExecuteAsync(fun () ->
 | 
				
			||||||
 | 
					        r.Table(Table.Success)
 | 
				
			||||||
 | 
					          .RunResultAsync<StoryEntry list> conn)
 | 
				
			||||||
 | 
				
			|||||||
@ -86,6 +86,7 @@ module Helpers =
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Handlers for /api/citizen routes
 | 
					/// Handlers for /api/citizen routes
 | 
				
			||||||
 | 
					[<RequireQualifiedAccess>]
 | 
				
			||||||
module Citizen =
 | 
					module Citizen =
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // GET: /api/citizen/log-on/[code]
 | 
					  // GET: /api/citizen/log-on/[code]
 | 
				
			||||||
@ -161,6 +162,18 @@ module Continent =
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Handlers for /api/listing[s] routes
 | 
				
			||||||
 | 
					[<RequireQualifiedAccess>]
 | 
				
			||||||
 | 
					module Listing =
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // GET: /api/listing/mine
 | 
				
			||||||
 | 
					  let mine : HttpHandler =
 | 
				
			||||||
 | 
					    authorize
 | 
				
			||||||
 | 
					    >=> fun next ctx -> task {
 | 
				
			||||||
 | 
					      let! listings = Data.Listing.findByCitizen (currentCitizenId ctx) (conn ctx)
 | 
				
			||||||
 | 
					      return! json listings next ctx
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Handlers for /api/profile routes
 | 
					/// Handlers for /api/profile routes
 | 
				
			||||||
[<RequireQualifiedAccess>]
 | 
					[<RequireQualifiedAccess>]
 | 
				
			||||||
module Profile =
 | 
					module Profile =
 | 
				
			||||||
@ -246,4 +259,118 @@ module Profile =
 | 
				
			|||||||
      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
 | 
				
			||||||
 | 
					  let search : HttpHandler =
 | 
				
			||||||
 | 
					    authorize
 | 
				
			||||||
 | 
					    >=> fun next ctx -> task {
 | 
				
			||||||
 | 
					      let  search  = ctx.BindQueryString<ProfileSearch> ()
 | 
				
			||||||
 | 
					      let! results = Data.Profile.search search (conn ctx)
 | 
				
			||||||
 | 
					      return! json results next ctx
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // GET: /api/profile/public-search
 | 
				
			||||||
 | 
					  let publicSearch : HttpHandler =
 | 
				
			||||||
 | 
					    fun next ctx -> task {
 | 
				
			||||||
 | 
					      let  search  = ctx.BindQueryString<PublicSearch> ()
 | 
				
			||||||
 | 
					      let! results = Data.Profile.publicSearch search (conn ctx)
 | 
				
			||||||
 | 
					      return! json results next ctx
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Handlers for /api/success routes
 | 
				
			||||||
 | 
					[<RequireQualifiedAccess>]
 | 
				
			||||||
 | 
					module Success =
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  open System
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // GET: /api/success/[id]
 | 
				
			||||||
 | 
					  let get successId : HttpHandler =
 | 
				
			||||||
 | 
					    authorize
 | 
				
			||||||
 | 
					    >=> fun next ctx -> task {
 | 
				
			||||||
 | 
					      match! Data.Success.findById (SuccessId successId) (conn ctx) with
 | 
				
			||||||
 | 
					      | Some story -> return! json story next ctx
 | 
				
			||||||
 | 
					      | None -> return! Error.notFound next ctx
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // GET: /api/success/list
 | 
				
			||||||
 | 
					  let all : HttpHandler =
 | 
				
			||||||
 | 
					    authorize
 | 
				
			||||||
 | 
					    >=> fun next ctx -> task {
 | 
				
			||||||
 | 
					      let! stories = Data.Success.all (conn ctx)
 | 
				
			||||||
 | 
					      return! json stories next ctx
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // POST: /api/success/save
 | 
				
			||||||
 | 
					  let save : HttpHandler =
 | 
				
			||||||
 | 
					    authorize
 | 
				
			||||||
 | 
					    >=> fun next ctx -> task {
 | 
				
			||||||
 | 
					      let  citizenId = currentCitizenId ctx
 | 
				
			||||||
 | 
					      let  dbConn    = conn ctx
 | 
				
			||||||
 | 
					      let  now       = (clock ctx).GetCurrentInstant ()
 | 
				
			||||||
 | 
					      let! form      = ctx.BindJsonAsync<StoryForm> ()
 | 
				
			||||||
 | 
					      let! success = task {
 | 
				
			||||||
 | 
					        match form.id with
 | 
				
			||||||
 | 
					        | "new" ->
 | 
				
			||||||
 | 
					            return Some { id         = (Guid.NewGuid >> SuccessId) ()
 | 
				
			||||||
 | 
					                          citizenId  = citizenId
 | 
				
			||||||
 | 
					                          recordedOn = now
 | 
				
			||||||
 | 
					                          fromHere   = form.fromHere
 | 
				
			||||||
 | 
					                          source     = "profile"
 | 
				
			||||||
 | 
					                          story      = noneIfEmpty form.story |> Option.map Text
 | 
				
			||||||
 | 
					                          }
 | 
				
			||||||
 | 
					        | successId ->
 | 
				
			||||||
 | 
					            match! Data.Success.findById (SuccessId.ofString successId) dbConn with
 | 
				
			||||||
 | 
					            | Some story when story.citizenId = citizenId ->
 | 
				
			||||||
 | 
					                return Some { story with
 | 
				
			||||||
 | 
					                                fromHere = form.fromHere
 | 
				
			||||||
 | 
					                                story    = noneIfEmpty form.story |> Option.map Text
 | 
				
			||||||
 | 
					                              }
 | 
				
			||||||
 | 
					            | Some _ | None -> return None
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      match success with
 | 
				
			||||||
 | 
					      | Some story ->
 | 
				
			||||||
 | 
					          do! Data.Success.save story dbConn
 | 
				
			||||||
 | 
					          return! ok next ctx
 | 
				
			||||||
 | 
					      | None -> return! Error.notFound next ctx
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					open Giraffe.EndpointRouting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// All available endpoints for the application
 | 
				
			||||||
 | 
					let allEndpoints = [
 | 
				
			||||||
 | 
					  subRoute "/api" [
 | 
				
			||||||
 | 
					    subRoute "/citizen" [
 | 
				
			||||||
 | 
					      GET_HEAD [
 | 
				
			||||||
 | 
					        routef "/log-on/%s" Citizen.logOn
 | 
				
			||||||
 | 
					        routef "/get/%O"    Citizen.get
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      DELETE [ route "" Citizen.delete ]
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    GET_HEAD [ route "/continent/all" Continent.all ]
 | 
				
			||||||
 | 
					    subRoute "/listing" [
 | 
				
			||||||
 | 
					      GET_HEAD [
 | 
				
			||||||
 | 
					        route "s/mine" Listing.mine
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    subRoute "/profile" [
 | 
				
			||||||
 | 
					      GET_HEAD [
 | 
				
			||||||
 | 
					        route  ""               Profile.current
 | 
				
			||||||
 | 
					        route  "/count"         Profile.count
 | 
				
			||||||
 | 
					        routef "/get/%O"        Profile.get
 | 
				
			||||||
 | 
					        route  "/public-search" Profile.publicSearch
 | 
				
			||||||
 | 
					        route  "/search"        Profile.search
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      PATCH [ route "/employment-found" Profile.employmentFound ]
 | 
				
			||||||
 | 
					      POST [ route "/save" Profile.save ]
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    subRoute "/success" [
 | 
				
			||||||
 | 
					      GET_HEAD [
 | 
				
			||||||
 | 
					        routef "/get/%O" Success.get
 | 
				
			||||||
 | 
					        route  "/list"   Success.all
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      POST [ route "/save" Success.save ]
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  ]
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@
 | 
				
			|||||||
module JobsJobsJobs.Domain.SharedTypes
 | 
					module JobsJobsJobs.Domain.SharedTypes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
open JobsJobsJobs.Domain.Types
 | 
					open JobsJobsJobs.Domain.Types
 | 
				
			||||||
 | 
					open NodaTime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// fsharplint:disable FieldNames
 | 
					// fsharplint:disable FieldNames
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -102,3 +103,84 @@ module ProfileSearch =
 | 
				
			|||||||
      match search.remoteWork with "" -> Some search.remoteWork | _ -> None
 | 
					      match search.remoteWork with "" -> Some search.remoteWork | _ -> None
 | 
				
			||||||
      ]
 | 
					      ]
 | 
				
			||||||
    |> List.exists Option.isSome
 | 
					    |> List.exists Option.isSome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// A user matching the profile search
 | 
				
			||||||
 | 
					type ProfileSearchResult = {
 | 
				
			||||||
 | 
					  // The ID of the citizen
 | 
				
			||||||
 | 
					  citizenId         : CitizenId
 | 
				
			||||||
 | 
					  // The citizen's display name
 | 
				
			||||||
 | 
					  displayName       : string
 | 
				
			||||||
 | 
					  // Whether this citizen is currently seeking employment
 | 
				
			||||||
 | 
					  seekingEmployment : bool
 | 
				
			||||||
 | 
					  // Whether this citizen is looking for remote work
 | 
				
			||||||
 | 
					  remoteWork        : bool
 | 
				
			||||||
 | 
					  // Whether this citizen is looking for full-time work
 | 
				
			||||||
 | 
					  fullTime          : bool
 | 
				
			||||||
 | 
					  // When this profile was last updated
 | 
				
			||||||
 | 
					  lastUpdated       : Instant
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// The parameters for a public job search
 | 
				
			||||||
 | 
					type PublicSearch = {
 | 
				
			||||||
 | 
					  /// Retrieve citizens from this continent
 | 
				
			||||||
 | 
					  continentId : string option
 | 
				
			||||||
 | 
					  /// Retrieve citizens from this region
 | 
				
			||||||
 | 
					  region : string option
 | 
				
			||||||
 | 
					  /// Text for a search within a citizen's skills
 | 
				
			||||||
 | 
					  skill : string option
 | 
				
			||||||
 | 
					  /// Whether to retrieve citizens who do or do not want remote work
 | 
				
			||||||
 | 
					  remoteWork : string
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Support functions for pblic searches
 | 
				
			||||||
 | 
					module PublicSearch =
 | 
				
			||||||
 | 
					  /// Is the search empty?
 | 
				
			||||||
 | 
					  let isEmptySearch (srch : PublicSearch) =
 | 
				
			||||||
 | 
					    [ srch.continentId
 | 
				
			||||||
 | 
					      srch.skill
 | 
				
			||||||
 | 
					      match srch.remoteWork with "" -> Some srch.remoteWork | _ -> None
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    |> List.exists Option.isSome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// A public profile search result
 | 
				
			||||||
 | 
					type PublicSearchResult = {
 | 
				
			||||||
 | 
					  /// The name of the continent on which the citizen resides
 | 
				
			||||||
 | 
					  continent  : string
 | 
				
			||||||
 | 
					  /// The region in which the citizen resides
 | 
				
			||||||
 | 
					  region     : string
 | 
				
			||||||
 | 
					  /// Whether this citizen is seeking remote work
 | 
				
			||||||
 | 
					  remoteWork : bool
 | 
				
			||||||
 | 
					  /// The skills this citizen has identified
 | 
				
			||||||
 | 
					  skills     : string list
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// The data required to provide a success story
 | 
				
			||||||
 | 
					type StoryForm = {
 | 
				
			||||||
 | 
					  /// The ID of this story
 | 
				
			||||||
 | 
					  id       : string
 | 
				
			||||||
 | 
					  /// Whether the employment was obtained from Jobs, Jobs, Jobs
 | 
				
			||||||
 | 
					  fromHere : bool
 | 
				
			||||||
 | 
					  /// The success story
 | 
				
			||||||
 | 
					  story    : string
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// An entry in the list of success stories
 | 
				
			||||||
 | 
					type StoryEntry = {
 | 
				
			||||||
 | 
					  /// The ID of this success story
 | 
				
			||||||
 | 
					  id          : SuccessId
 | 
				
			||||||
 | 
					  /// The ID of the citizen who recorded this story
 | 
				
			||||||
 | 
					  citizenId   : CitizenId
 | 
				
			||||||
 | 
					  /// The name of the citizen who recorded this story
 | 
				
			||||||
 | 
					  citizenName : string
 | 
				
			||||||
 | 
					  /// When this story was recorded
 | 
				
			||||||
 | 
					  RecordedOn  : Instant
 | 
				
			||||||
 | 
					  /// Whether this story involves an opportunity that arose due to Jobs, Jobs, Jobs
 | 
				
			||||||
 | 
					  fromHere    : bool
 | 
				
			||||||
 | 
					  /// Whether this report has a further story, or if it is simply a "found work" entry
 | 
				
			||||||
 | 
					  hasStory    : bool
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user