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.parameters [ "@id", (CitizenId.toString >> Sql.string) citizenId ]
|> Sql.executeRowAsync fromReader |> Sql.executeRowAsync fromReader
|> noneIfNotFound |> 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 /// Whether information from this profile should appear in the public anonymous list of available skills
isPublic : bool isPublic : bool
/// The continent on which the user is seeking employment /// 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 /// The region within that continent where the user would prefer to work
region : string region : string
/// Whether the user is looking for remote work /// Whether the user is looking for remote work

View File

@ -123,13 +123,32 @@ module Citizen =
} }
/// /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 open Suave.Filters
/// The routes for Jobs, Jobs, Jobs /// The routes for Jobs, Jobs, Jobs
let webApp = let webApp =
choose choose
[ GET >=> choose [ GET >=> choose
[ path "/" >=> Vue.app [ pathScan "/api/profile/%s" Profile.get
path "/api/profile" >=> Profile.get ""
path "/" >=> Vue.app
Files.browse "wwwroot/" Files.browse "wwwroot/"
] ]
// PUT >=> choose // PUT >=> choose

View File

@ -1,5 +1,11 @@
import { ref } from 'vue'
import { Profile } from './types'
/** /**
* Jobs, Jobs, Jobs API interface * Jobs, Jobs, Jobs API interface
*
* @author Daniel J. Summers <daniel@bitbadger.solutions>
* @version 1
*/ */
/** The base URL for the Jobs, Jobs, Jobs API */ /** 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 if (method === 'POST' && payload) options.body = payload
const actualUrl = (options.method === 'GET' && payload) ? `url?${payload}` : url const actualUrl = (options.method === 'GET' && payload) ? `url?${payload}` : url
const resp = await fetch(actualUrl, options) 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}`) 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 jwt.token = jjjToken.accessToken
return true 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> <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> </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>