From 2f84821d11cd5ce435cfc2f816d4c4ad709e37c2 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Mon, 23 Nov 2020 22:31:57 -0500 Subject: [PATCH] Retrieve existing profile for welcome page (#2) --- src/JobsJobsJobs.Api/Data.fs | 36 +++++++++++++ src/JobsJobsJobs.Api/Domain.fs | 2 +- src/JobsJobsJobs.Api/Handlers.fs | 23 +++++++- src/jobs-jobs-jobs/src/api/index.ts | 22 +++++++- src/jobs-jobs-jobs/src/api/types.ts | 52 +++++++++++++++++++ .../src/views/citizen/Welcome.vue | 26 +++++++++- 6 files changed, 156 insertions(+), 5 deletions(-) create mode 100644 src/jobs-jobs-jobs/src/api/types.ts diff --git a/src/JobsJobsJobs.Api/Data.fs b/src/JobsJobsJobs.Api/Data.fs index e198744..1bf04c7 100644 --- a/src/JobsJobsJobs.Api/Data.fs +++ b/src/JobsJobsJobs.Api/Data.fs @@ -96,3 +96,39 @@ module Citizens = |> Sql.parameters [ "@id", (CitizenId.toString >> Sql.string) citizenId ] |> Sql.executeRowAsync fromReader |> noneIfNotFound + + +/// Functions for manipulating employment profiles +module Profiles = + + /// Create a Profile from a row of data + let private fromReader (read: RowReader) = + match (read.string >> CitizenId.tryParse) "citizen_id" with + | Ok citizenId -> + match (read.string >> ContinentId.tryParse) "continent_id" with + | Ok continentId -> { + citizenId = citizenId + seekingEmployment = read.bool "seeking_employment" + isPublic = read.bool "is_public" + continent = { id = continentId; name = read.string "continent_name" } + region = read.string "region" + remoteWork = read.bool "remote_work" + fullTime = read.bool "full_time" + biography = (read.string >> MarkdownString) "biography" + lastUpdatedOn = (read.int64 >> Millis) "last_updated_on" + experience = (read.stringOrNone >> Option.map MarkdownString) "experience" + } + | Error err -> failwith err + | Error err -> failwith err + + /// Try to find an employment profile for the given citizen ID + let tryFind citizenId = + db () + |> Sql.query + """SELECT p.*, c.name AS continent_name + FROM profile p + INNER JOIN continent c ON p.continent_id = c.id + WHERE citizen_id = @id""" + |> Sql.parameters [ "@id", (CitizenId.toString >> Sql.string) citizenId ] + |> Sql.executeRowAsync fromReader + |> noneIfNotFound diff --git a/src/JobsJobsJobs.Api/Domain.fs b/src/JobsJobsJobs.Api/Domain.fs index fdc8e97..4c42392 100644 --- a/src/JobsJobsJobs.Api/Domain.fs +++ b/src/JobsJobsJobs.Api/Domain.fs @@ -185,7 +185,7 @@ type Profile = { /// Whether information from this profile should appear in the public anonymous list of available skills isPublic : bool /// The continent on which the user is seeking employment - continentId : Continent + continent : Continent /// The region within that continent where the user would prefer to work region : string /// Whether the user is looking for remote work diff --git a/src/JobsJobsJobs.Api/Handlers.fs b/src/JobsJobsJobs.Api/Handlers.fs index ae57669..b7ea4e7 100644 --- a/src/JobsJobsJobs.Api/Handlers.fs +++ b/src/JobsJobsJobs.Api/Handlers.fs @@ -121,7 +121,24 @@ module Citizen = | Error msg -> return! Error.error (exn msg) "Could not authenticate with NAS" ctx | Error exn -> return! Error.error exn "Token not received" ctx } - + + +/// /api/profile route handlers +module Profile = + + /// GET: /api/profile + let get citizenId : WebPart = + authorizedUser + >=> fun ctx -> async { + match (match citizenId with "" -> Ok (currentCitizenId ctx) | _ -> CitizenId.tryParse citizenId) with + | Ok citId -> + match! Profiles.tryFind citId with + | Ok (Some profile) -> return! json profile ctx + | Ok None -> return! Error.notFound ctx + | Error exn -> return! Error.error exn "Cannot retrieve profile" ctx + | Error _ -> return! Error.notFound ctx + } + open Suave.Filters @@ -129,7 +146,9 @@ open Suave.Filters let webApp = choose [ GET >=> choose - [ path "/" >=> Vue.app + [ pathScan "/api/profile/%s" Profile.get + path "/api/profile" >=> Profile.get "" + path "/" >=> Vue.app Files.browse "wwwroot/" ] // PUT >=> choose diff --git a/src/jobs-jobs-jobs/src/api/index.ts b/src/jobs-jobs-jobs/src/api/index.ts index 0850af8..41b020f 100644 --- a/src/jobs-jobs-jobs/src/api/index.ts +++ b/src/jobs-jobs-jobs/src/api/index.ts @@ -1,5 +1,11 @@ +import { ref } from 'vue' +import { Profile } from './types' + /** * Jobs, Jobs, Jobs API interface + * + * @author Daniel J. Summers + * @version 1 */ /** The base URL for the Jobs, Jobs, Jobs API */ @@ -59,7 +65,7 @@ export async function doRequest(url: string, method?: string, payload?: string) if (method === 'POST' && payload) options.body = payload const actualUrl = (options.method === 'GET' && payload) ? `url?${payload}` : url const resp = await fetch(actualUrl, options) - if (resp.ok) return resp + if (resp.ok || resp.status === 404) return resp throw new Error(`Error executing API request: ${resp.status} ~ ${resp.statusText}`) } @@ -75,3 +81,17 @@ export async function jjjAuthorize(nasToken: string): Promise { jwt.token = jjjToken.accessToken return true } + +/** + * Retrieve the employment profile for the current user. + * + * @returns The profile if it is found; undefined otherwise + */ +export async function userProfile(): Promise { + const resp = await doRequest(`${API_URL}/profile`) + if (resp.status === 404) { + return undefined + } + const profile = await resp.json() + return profile as Profile +} diff --git a/src/jobs-jobs-jobs/src/api/types.ts b/src/jobs-jobs-jobs/src/api/types.ts new file mode 100644 index 0000000..6b53812 --- /dev/null +++ b/src/jobs-jobs-jobs/src/api/types.ts @@ -0,0 +1,52 @@ +/** + * Client-side Type Definitions for Jobs, Jobs, Jobs. + * + * @author Daniel J. Summers + * @version 1 + */ + +/** + * A continent (one of the 7). + */ +export interface Continent { + /** The ID of the continent */ + id: string + + /** The name of the continent */ + name: string +} + +/** + * A user's employment profile. + */ +export interface Profile { + /** The ID of the user to whom the profile applies */ + citizenId: string + + /** Whether this user is actively seeking employment */ + seekingEmployment: boolean + + /** Whether information from this profile should appear in the public anonymous list of available skills */ + isPublic: boolean + + /** The continent on which the user is seeking employment */ + continent: Continent + + /** The region within that continent where the user would prefer to work */ + region: string + + /** Whether the user is looking for remote work */ + remoteWork: boolean + + /** Whether the user is looking for full-time work */ + fullTime: boolean + + /** The user's professional biography */ + biography: string + + /** When this profile was last updated */ + lastUpdatedOn: number + + /** The user's experience */ + experience?: string +} diff --git a/src/jobs-jobs-jobs/src/views/citizen/Welcome.vue b/src/jobs-jobs-jobs/src/views/citizen/Welcome.vue index 4645819..697dd26 100644 --- a/src/jobs-jobs-jobs/src/views/citizen/Welcome.vue +++ b/src/jobs-jobs-jobs/src/views/citizen/Welcome.vue @@ -1,3 +1,27 @@ + + \ No newline at end of file