From 415cbbf65080806733a210beb10e0b917261b0d5 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Sat, 14 Jan 2023 22:41:08 -0500 Subject: [PATCH] Migrate public profile search --- .../components/profile/PublicSearchForm.vue | 78 ---- src/JobsJobsJobs/App/src/router/index.ts | 19 - .../App/src/views/PrivacyPolicy.vue | 430 ------------------ .../App/src/views/TermsOfService.vue | 44 -- .../App/src/views/profile/Seeking.vue | 126 ----- src/JobsJobsJobs/Data/Data.fs | 28 +- src/JobsJobsJobs/Domain/SharedTypes.fs | 19 +- src/JobsJobsJobs/Server/Handlers.fs | 26 +- src/JobsJobsJobs/Server/Views/Profile.fs | 84 ++++ 9 files changed, 119 insertions(+), 735 deletions(-) delete mode 100644 src/JobsJobsJobs/App/src/components/profile/PublicSearchForm.vue delete mode 100644 src/JobsJobsJobs/App/src/views/PrivacyPolicy.vue delete mode 100644 src/JobsJobsJobs/App/src/views/TermsOfService.vue delete mode 100644 src/JobsJobsJobs/App/src/views/profile/Seeking.vue diff --git a/src/JobsJobsJobs/App/src/components/profile/PublicSearchForm.vue b/src/JobsJobsJobs/App/src/components/profile/PublicSearchForm.vue deleted file mode 100644 index 97a2298..0000000 --- a/src/JobsJobsJobs/App/src/components/profile/PublicSearchForm.vue +++ /dev/null @@ -1,78 +0,0 @@ - - - diff --git a/src/JobsJobsJobs/App/src/router/index.ts b/src/JobsJobsJobs/App/src/router/index.ts index 49fe403..9fe6b8b 100644 --- a/src/JobsJobsJobs/App/src/router/index.ts +++ b/src/JobsJobsJobs/App/src/router/index.ts @@ -36,18 +36,6 @@ const routes: Array = [ component: () => import(/* webpackChunkName: "help" */ "../views/HowItWorks.vue"), meta: { auth: false, title: "How It Works" } }, - { - path: "/privacy-policy", - name: "PrivacyPolicy", - component: () => import(/* webpackChunkName: "legal" */ "../views/PrivacyPolicy.vue"), - meta: { auth: false, title: "Privacy Policy" } - }, - { - path: "/terms-of-service", - name: "TermsOfService", - component: () => import(/* webpackChunkName: "legal" */ "../views/TermsOfService.vue"), - meta: { auth: false, title: "Terms of Service" } - }, // Citizen URLs { path: "/citizen/account", @@ -86,13 +74,6 @@ const routes: Array = [ component: () => import(/* webpackChunkName: "joblist" */ "../views/listing/MyListings.vue"), meta: { auth: true, title: "My Job Listings" } }, - // Profile URLs - { - path: "/profile/seeking", - name: "PublicSearchProfiles", - component: () => import(/* webpackChunkName: "seeking" */ "../views/profile/Seeking.vue"), - meta: { auth: false, title: "People Seeking Work" } - }, // "So Long" URLs { path: "/so-long/options", diff --git a/src/JobsJobsJobs/App/src/views/PrivacyPolicy.vue b/src/JobsJobsJobs/App/src/views/PrivacyPolicy.vue deleted file mode 100644 index 174e789..0000000 --- a/src/JobsJobsJobs/App/src/views/PrivacyPolicy.vue +++ /dev/null @@ -1,430 +0,0 @@ - - - diff --git a/src/JobsJobsJobs/App/src/views/TermsOfService.vue b/src/JobsJobsJobs/App/src/views/TermsOfService.vue deleted file mode 100644 index 30d6838..0000000 --- a/src/JobsJobsJobs/App/src/views/TermsOfService.vue +++ /dev/null @@ -1,44 +0,0 @@ - diff --git a/src/JobsJobsJobs/App/src/views/profile/Seeking.vue b/src/JobsJobsJobs/App/src/views/profile/Seeking.vue deleted file mode 100644 index c622847..0000000 --- a/src/JobsJobsJobs/App/src/views/profile/Seeking.vue +++ /dev/null @@ -1,126 +0,0 @@ - - - diff --git a/src/JobsJobsJobs/Data/Data.fs b/src/JobsJobsJobs/Data/Data.fs index 302e954..6db56e8 100644 --- a/src/JobsJobsJobs/Data/Data.fs +++ b/src/JobsJobsJobs/Data/Data.fs @@ -483,29 +483,29 @@ module Profiles = } // Search profiles (public) - let publicSearch (search : PublicSearch) = + let publicSearch (search : PublicSearchForm) = let searches = [ - match search.ContinentId with - | Some contId -> "p.data ->> 'continentId' = @continentId", [ "@continentId", Sql.string contId ] - | None -> () - match search.Region with - | Some region -> "p.data ->> 'region' ILIKE @region", [ "@region", like region ] - | None -> () + if search.ContinentId <> "" then + "p.data ->> 'continentId' = @continentId", [ "@continentId", Sql.string search.ContinentId ] + if search.Region <> "" then + "p.data ->> 'region' ILIKE @region", [ "@region", like search.Region ] if search.RemoteWork <> "" then - "p.data ->> 'remoteWork' = @remote", [ "@remote", jsonBool (search.RemoteWork = "yes") ] - match search.Skill with - | Some skl -> - "p.data -> 'skills' ->> 'description' ILIKE @description", [ "@description", like skl ] - | None -> () + "p.data ->> 'isRemote' = @remote", [ "@remote", jsonBool (search.RemoteWork = "yes") ] + if search.Skill <> "" then + "EXISTS ( + SELECT 1 FROM jsonb_array_elements(p.data['skills']) x(elt) + WHERE x ->> 'description' ILIKE @description)", + [ "@description", like search.Skill ] ] connection () |> Sql.query $" SELECT p.*, c.data AS cont_data FROM {Table.Profile} p INNER JOIN {Table.Continent} c ON c.id = p.data ->> 'continentId' - WHERE p.data ->> 'isPublic' = 'true' - AND p.data ->> 'isLegacy' = 'false' + WHERE p.data ->> 'isPubliclySearchable' = 'true' + AND p.data ->> 'isLegacy' = 'false' {searchSql searches}" + |> Sql.parameters (searches |> List.collect snd) |> Sql.executeAsync (fun row -> let profile = toDocument row let continent = toDocumentFrom "cont_data" row diff --git a/src/JobsJobsJobs/Domain/SharedTypes.fs b/src/JobsJobsJobs/Domain/SharedTypes.fs index 1255e45..1545b05 100644 --- a/src/JobsJobsJobs/Domain/SharedTypes.fs +++ b/src/JobsJobsJobs/Domain/SharedTypes.fs @@ -218,31 +218,20 @@ type ProfileForView = /// The parameters for a public job search [] -type PublicSearch = +type PublicSearchForm = { /// Retrieve citizens from this continent - ContinentId : string option + ContinentId : string /// Retrieve citizens from this region - Region : string option + Region : string /// Text for a search within a citizen's skills - Skill : string option + Skill : string /// Whether to retrieve citizens who do or do not want remote work RemoteWork : string } -/// Support functions for public searches -module PublicSearch = - /// Is the search empty? - let isEmptySearch (search : PublicSearch) = - [ search.ContinentId - search.Region - search.Skill - if search.RemoteWork = "" then Some search.RemoteWork else None - ] - |> List.exists Option.isSome - /// A public profile search result type PublicSearchResult = diff --git a/src/JobsJobsJobs/Server/Handlers.fs b/src/JobsJobsJobs/Server/Handlers.fs index 7d5fb66..11a6a9b 100644 --- a/src/JobsJobsJobs/Server/Handlers.fs +++ b/src/JobsJobsJobs/Server/Handlers.fs @@ -663,6 +663,22 @@ module Profile = return! Profile.search form continents (timeZone ctx) results |> render "Profile Search" next ctx } + // GET: /profile/seeking + let seeking : HttpHandler = fun next ctx -> task { + let! continents = Continents.all () + let form = + match ctx.TryBindQueryString () with + | Ok f -> f + | Error _ -> { ContinentId = ""; Region = ""; RemoteWork = ""; Skill = "" } + let! results = task { + if string ctx.Request.Query["searched"] = "true" then + let! it = Profiles.publicSearch form + return Some it + else return None + } + return! Profile.publicSearch form continents results |> render "Profile Search" next ctx + } + // GET: /profile/[id]/view let view citizenId : HttpHandler = fun next ctx -> task { let citId = CitizenId.ofString citizenId @@ -732,13 +748,6 @@ module ProfileApi = do! Profiles.deleteById (currentCitizenId ctx) return! ok next ctx } - - // GET: /api/profile/public-search - let publicSearch : HttpHandler = fun next ctx -> task { - let search = ctx.BindQueryString () - let! results = Profiles.publicSearch search - return! json results next ctx - } /// Handlers for /api/success routes @@ -815,6 +824,7 @@ let allEndpoints = [ routef "/%s/view" Profile.view route "/edit" Profile.edit route "/search" Profile.search + route "/seeking" Profile.seeking ] POST [ route "/save" Profile.save ] ] @@ -848,8 +858,6 @@ let allEndpoints = [ route "" ProfileApi.current route "/count" ProfileApi.count routef "/%O" ProfileApi.get - routef "/%O/view" ProfileApi.view - route "/public-search" ProfileApi.publicSearch ] PATCH [ route "/employment-found" ProfileApi.employmentFound ] ] diff --git a/src/JobsJobsJobs/Server/Views/Profile.fs b/src/JobsJobsJobs/Server/Views/Profile.fs index 88c8709..2508095 100644 --- a/src/JobsJobsJobs/Server/Views/Profile.fs +++ b/src/JobsJobsJobs/Server/Views/Profile.fs @@ -127,6 +127,90 @@ let edit (m : EditProfileViewModel) continents isNew citizenId csrf = ] +/// The public search page +let publicSearch (m : PublicSearchForm) continents (results : PublicSearchResult list option) = + article [] [ + h3 [ _class "pb-3" ] [ rawText "People Seeking Work" ] + if Option.isNone results then + p [] [ + rawText "Enter one or more criteria to filter results, or just click “Search” to list all " + rawText "publicly searchable profiles." + ] + collapsePanel "Search Criteria" [ + form [ _class "container"; _method "GET"; _action "/profile/seeking" ] [ + input [ _type "hidden"; _name "searched"; _value "true" ] + div [ _class "row" ] [ + div [ _class "col-12 col-sm-6 col-md-4 col-lg-3" ] [ + continentList [] "ContinentId" continents (Some "Any") m.ContinentId false + ] + div [ _class "col-12 col-sm-6 col-md-4 col-lg-3" ] [ + textBox [ _maxlength "1000" ] (nameof m.Region) m.Region "Region" false + div [ _class "form-text" ] [ rawText "(free-form text)" ] + ] + div [ _class "col-12 col-sm-6 col-offset-md-2 col-lg-3 col-offset-lg-0" ] [ + label [ _class "jjj-label" ] [ rawText "Seeking Remote Work?" ]; br [] + div [ _class "form-check form-check-inline" ] [ + input [ _type "radio"; _id "remoteNull"; _name (nameof m.RemoteWork); _value "" + _class "form-check-input"; if m.RemoteWork = "" then _checked ] + label [ _class "form-check-label"; _for "remoteNull" ] [ rawText "No Selection" ] + ] + div [ _class "form-check form-check-inline" ] [ + input [ _type "radio"; _id "remoteYes"; _name (nameof m.RemoteWork); _value "yes" + _class "form-check-input"; if m.RemoteWork = "yes" then _checked ] + label [ _class "form-check-label"; _for "remoteYes" ] [ rawText "Yes" ] + ] + div [ _class "form-check form-check-inline" ] [ + input [ _type "radio"; _id "remoteNo"; _name (nameof m.RemoteWork); _value "no" + _class "form-check-input"; if m.RemoteWork = "no" then _checked ] + label [ _class "form-check-label"; _for "remoteNo" ] [ rawText "No" ] + ] + ] + div [ _class "col-12 col-sm-6 col-lg-3" ] [ + textBox [ _maxlength "1000" ] (nameof m.Skill) m.Skill "Skill" false + div [ _class "form-text" ] [ rawText "(free-form text)" ] + ] + ] + div [ _class "row" ] [ + div [ _class "col" ] [ + br [] + button [ _type "submit"; _class "btn btn-outline-primary" ] [ rawText "Search" ] + ] + ] + ] + ] + match results with + | Some r when List.isEmpty r -> p [ _class "pt-3" ] [ rawText "No results found for the specified criteria" ] + | Some r -> + p [ _class "py-3" ] [ + rawText "These profiles match your search criteria. To learn more about these people, join the merry " + rawText "band of human resources in the " + a [ _href "https://noagendashow.net"; _target "_blank"; _rel "noopener" ] [ rawText "No Agenda" ] + rawText " tribe!" + ] + table [ _class "table table-sm table-hover" ] [ + thead [] [ + tr [] [ + th [ _scope "col" ] [ rawText "Continent" ] + th [ _scope "col"; _class "text-center" ] [ rawText "Region" ] + th [ _scope "col"; _class "text-center" ] [ rawText "Remote?" ] + th [ _scope "col"; _class "text-center" ] [ rawText "Skills" ] + ] + ] + r |> List.map (fun profile -> + tr [] [ + td [] [ str profile.Continent ] + td [] [ str profile.Region ] + td [ _class "text-center" ] [ rawText (yesOrNo profile.RemoteWork) ] + profile.Skills + |> List.collect (fun skill -> [ str skill; br [] ]) + |> td [] + ]) + |> tbody [] + ] + | None -> () + ] + + /// Logged-on search page let search (m : ProfileSearchForm) continents tz (results : ProfileSearchResult list option) = article [] [