Retrieve existing profile for welcome page (#2)

This commit is contained in:
Daniel J. Summers 2020-11-23 22:31:57 -05:00
parent daece3eab1
commit 2f84821d11
6 changed files with 156 additions and 5 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,5 +1,11 @@
import { ref } from 'vue'
import { Profile } from './types'
/**
* Jobs, Jobs, Jobs API interface
*
* @author Daniel J. Summers <daniel@bitbadger.solutions>
* @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<boolean> {
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<Profile | undefined> {
const resp = await doRequest(`${API_URL}/profile`)
if (resp.status === 404) {
return undefined
}
const profile = await resp.json()
return profile as Profile
}

View File

@ -0,0 +1,52 @@
/**
* Client-side Type Definitions for Jobs, Jobs, Jobs.
*
* @author Daniel J. Summers <daniel@bitbadger.solutions>
* @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
}

View File

@ -1,3 +1,27 @@
<template>
<div>Welcome</div>
<div>
<p>Welcome!</p>
<p>Profile Established: <strong><span v-if="profile?.value">Yes</span><span v-else>No</span></strong></p>
</div>
</template>
<script lang="ts">
import { onBeforeMount, ref } from 'vue'
import { userProfile } from '@/api'
import { Profile } from '@/api/types'
export default {
setup() {
const profile = ref<Profile | undefined>(undefined)
onBeforeMount(async () => {
profile.value = await userProfile()
})
return {
profile
}
}
}
</script>