diff --git a/src/JobsJobsJobs/Api/App.fs b/src/JobsJobsJobs/Api/App.fs index c88a7f8..c7b704f 100644 --- a/src/JobsJobsJobs/Api/App.fs +++ b/src/JobsJobsJobs/Api/App.fs @@ -22,6 +22,7 @@ let configureApp (app : IApplicationBuilder) = e.MapFallbackToFile "index.html" |> ignore) |> ignore +open Newtonsoft.Json open NodaTime open Microsoft.AspNetCore.Authentication.JwtBearer open Microsoft.Extensions.Configuration @@ -36,6 +37,10 @@ let configureServices (svc : IServiceCollection) = svc.AddLogging () |> ignore svc.AddCors () |> ignore + let jsonCfg = JsonSerializerSettings () + Data.Converters.all () |> List.iter jsonCfg.Converters.Add + svc.AddSingleton (NewtonsoftJson.Serializer jsonCfg) |> ignore + let svcs = svc.BuildServiceProvider () let cfg = svcs.GetRequiredService () diff --git a/src/JobsJobsJobs/App/src/api/index.ts b/src/JobsJobsJobs/App/src/api/index.ts index fa2fbb5..06346d6 100644 --- a/src/JobsJobsJobs/App/src/api/index.ts +++ b/src/JobsJobsJobs/App/src/api/index.ts @@ -1,4 +1,4 @@ -import { Count, LogOnSuccess, Profile } from './types' +import { Citizen, Continent, Count, LogOnSuccess, Profile } from './types' /** * Create a URL that will access the API @@ -39,6 +39,46 @@ export default { const resp = await fetch(apiUrl(`citizen/log-on/${code}`), { method: 'GET', mode: 'cors' }) if (resp.status === 200) return await resp.json() as LogOnSuccess return `Error logging on - ${await resp.text()}` + }, + + /** + * Retrieve a citizen by their ID + * + * @param id The citizen ID to be retrieved + * @param user The currently logged-on user + * @returns The citizen, or an error + */ + retrieve: async (id : string, user : LogOnSuccess) : Promise => { + const resp = await fetch(apiUrl(`citizen/get/${id}`), reqInit('GET', user)) + if (resp.status === 200) return await resp.json() as Citizen + return `Error retrieving citizen ${id} - ${await resp.text()}` + }, + + /** + * Delete the current citizen's entire Jobs, Jobs, Jobs record + * + * @param user The currently logged-on user + * @returns Undefined if successful, an error if not + */ + delete: async (user : LogOnSuccess) : Promise => { + const resp = await fetch(apiUrl('citizen'), reqInit('DELETE', user)) + if (resp.status === 200) return undefined + return `Error deleting citizen - ${await resp.text()}` + } + }, + + /** API functions for continents */ + continent: { + + /** + * Get all continents + * + * @returns All continents, or an error + */ + all: async () : Promise => { + const resp = await fetch(apiUrl('continent/all'), { method: 'GET' }) + if (resp.status === 200) return await resp.json() as Continent[] + return `Error retrieving continents - ${await resp.text()}` } }, @@ -71,7 +111,19 @@ export default { const result = await resp.json() as Count return result.count } - return `Error counting profiles = ${await resp.text()}` + return `Error counting profiles - ${await resp.text()}` + }, + + /** + * Delete the current user's employment profile + * + * @param user The currently logged-on user + * @returns Undefined if successful, an error if not + */ + delete: async (user : LogOnSuccess) : Promise => { + const resp = await fetch(apiUrl('profile'), reqInit('DELETE', user)) + if (resp.status === 200) return undefined + return `Error deleting profile - ${await resp.text()}` } } } diff --git a/src/JobsJobsJobs/App/src/api/types.ts b/src/JobsJobsJobs/App/src/api/types.ts index b797d28..0648aea 100644 --- a/src/JobsJobsJobs/App/src/api/types.ts +++ b/src/JobsJobsJobs/App/src/api/types.ts @@ -1,4 +1,30 @@ +/** A user of Jobs, Jobs, Jobs */ +export interface Citizen { + /** The ID of the user */ + id : string + /** The handle by which the user is known on Mastodon */ + naUser : string + /** The user's display name from Mastodon (updated every login) */ + displayName : string | undefined + /** The user's real name */ + realName : string | undefined + /** The URL for the user's Mastodon profile */ + profileUrl : string + /** When the user joined Jobs, Jobs, Jobs */ + joinedOn : number + /** When the user last logged in */ + lastSeenOn : number +} + +/** A continent */ +export interface Continent { + /** The ID of the continent */ + id : string + /** The name of the continent */ + name : string +} + /** A successful logon */ export interface LogOnSuccess { /** The JSON Web Token (JWT) to use for API access */ diff --git a/src/JobsJobsJobs/App/src/components/MarkdownEditor.vue b/src/JobsJobsJobs/App/src/components/MarkdownEditor.vue new file mode 100644 index 0000000..989cbba --- /dev/null +++ b/src/JobsJobsJobs/App/src/components/MarkdownEditor.vue @@ -0,0 +1,52 @@ + + + diff --git a/src/JobsJobsJobs/App/src/components/shared/LoadData.vue b/src/JobsJobsJobs/App/src/components/shared/LoadData.vue new file mode 100644 index 0000000..0cf17c0 --- /dev/null +++ b/src/JobsJobsJobs/App/src/components/shared/LoadData.vue @@ -0,0 +1,48 @@ + + + diff --git a/src/JobsJobsJobs/App/src/router/index.ts b/src/JobsJobsJobs/App/src/router/index.ts index 11674aa..c1efda8 100644 --- a/src/JobsJobsJobs/App/src/router/index.ts +++ b/src/JobsJobsJobs/App/src/router/index.ts @@ -17,7 +17,42 @@ const routes: Array = [ }, { path: '/citizen/profile', - component: () => import(/* webpackChurchName: "profedit" */ '../views/citizen/EditProfile.vue') + component: () => import(/* webpackChunkName: "profedit" */ '../views/citizen/EditProfile.vue') + }, + { + path: '/citizen/log-off', + component: () => import(/* webpackChunkName: "logoff" */ '../views/citizen/LogOff.vue') + }, + // Profile URLs + { + path: '/profile/view/:id', + component: () => import(/* webpackChunkName: "profview" */ '../views/profile/ProfileView.vue') + }, + { + path: '/profile/search', + component: () => import(/* webpackChunkName: "profview" */ '../views/profile/ProfileSearch.vue') + }, + { + path: '/profile/seeking', + component: () => import(/* webpackChunkName: "seeking" */ '../views/profile/Seeking.vue') + }, + // "So Long" URLs + { + path: '/so-long/options', + component: () => import(/* webpackChunkName: "so-long" */ '../views/so-long/DeletionOptions.vue') + }, + { + path: '/so-long/success', + component: () => import(/* webpackChunkName: "so-long" */ '../views/so-long/DeletionSuccess.vue') + }, + // Success Story URLs + { + path: '/success-story/list', + component: () => import(/* webpackChunkName: "succview" */ '../views/success-story/StoryList.vue') + }, + { + path: '/success-story/add', + component: () => import(/* webpackChunkName: "succedit" */ '../views/success-story/StoryAdd.vue') } ] diff --git a/src/JobsJobsJobs/App/src/store/index.ts b/src/JobsJobsJobs/App/src/store/index.ts index b4e9818..79fa767 100644 --- a/src/JobsJobsJobs/App/src/store/index.ts +++ b/src/JobsJobsJobs/App/src/store/index.ts @@ -1,11 +1,12 @@ import { InjectionKey } from 'vue' import { createStore, Store, useStore as baseUseStore } from 'vuex' -import api, { LogOnSuccess } from '../api' +import api, { Continent, LogOnSuccess } from '../api' /** The state tracked by the application */ export interface State { user: LogOnSuccess | undefined logOnState: string + continents: Continent[] } /** An injection key to identify this state with Vue */ @@ -20,15 +21,22 @@ export default createStore({ state: () : State => { return { user: undefined, - logOnState: 'Logging you on with No Agenda Social...' + logOnState: 'Logging you on with No Agenda Social...', + continents: [] } }, mutations: { setUser (state, user: LogOnSuccess) { state.user = user }, + clearUser (state) { + state.user = undefined + }, setLogOnState (state, message) { state.logOnState = message + }, + setContinents (state, continents : Continent[]) { + state.continents = continents } }, actions: { @@ -39,6 +47,15 @@ export default createStore({ } else { commit('setUser', logOnResult) } + }, + async ensureContinents ({ state, commit }) { + if (state.continents.length > 0) return + const theSeven = await api.continent.all() + if (typeof theSeven === 'string') { + console.error(theSeven) + } else { + commit('setContinents', theSeven) + } } }, modules: { diff --git a/src/JobsJobsJobs/App/src/views/citizen/Dashboard.vue b/src/JobsJobsJobs/App/src/views/citizen/Dashboard.vue index 992188a..ecbe887 100644 --- a/src/JobsJobsJobs/App/src/views/citizen/Dashboard.vue +++ b/src/JobsJobsJobs/App/src/views/citizen/Dashboard.vue @@ -1,29 +1,30 @@ diff --git a/src/JobsJobsJobs/App/src/views/profile/ProfileSearch.vue b/src/JobsJobsJobs/App/src/views/profile/ProfileSearch.vue new file mode 100644 index 0000000..28bcbc5 --- /dev/null +++ b/src/JobsJobsJobs/App/src/views/profile/ProfileSearch.vue @@ -0,0 +1,3 @@ + diff --git a/src/JobsJobsJobs/App/src/views/profile/ProfileView.vue b/src/JobsJobsJobs/App/src/views/profile/ProfileView.vue new file mode 100644 index 0000000..810b021 --- /dev/null +++ b/src/JobsJobsJobs/App/src/views/profile/ProfileView.vue @@ -0,0 +1,20 @@ + + + diff --git a/src/JobsJobsJobs/App/src/views/profile/Seeking.vue b/src/JobsJobsJobs/App/src/views/profile/Seeking.vue new file mode 100644 index 0000000..28bcbc5 --- /dev/null +++ b/src/JobsJobsJobs/App/src/views/profile/Seeking.vue @@ -0,0 +1,3 @@ + diff --git a/src/JobsJobsJobs/App/src/views/so-long/DeletionOptions.vue b/src/JobsJobsJobs/App/src/views/so-long/DeletionOptions.vue new file mode 100644 index 0000000..df93896 --- /dev/null +++ b/src/JobsJobsJobs/App/src/views/so-long/DeletionOptions.vue @@ -0,0 +1,82 @@ + + + diff --git a/src/JobsJobsJobs/App/src/views/so-long/DeletionSuccess.vue b/src/JobsJobsJobs/App/src/views/so-long/DeletionSuccess.vue new file mode 100644 index 0000000..ed5efce --- /dev/null +++ b/src/JobsJobsJobs/App/src/views/so-long/DeletionSuccess.vue @@ -0,0 +1,12 @@ + diff --git a/src/JobsJobsJobs/App/src/views/success-story/StoryAdd.vue b/src/JobsJobsJobs/App/src/views/success-story/StoryAdd.vue new file mode 100644 index 0000000..28bcbc5 --- /dev/null +++ b/src/JobsJobsJobs/App/src/views/success-story/StoryAdd.vue @@ -0,0 +1,3 @@ + diff --git a/src/JobsJobsJobs/App/src/views/success-story/StoryList.vue b/src/JobsJobsJobs/App/src/views/success-story/StoryList.vue new file mode 100644 index 0000000..28bcbc5 --- /dev/null +++ b/src/JobsJobsJobs/App/src/views/success-story/StoryList.vue @@ -0,0 +1,3 @@ +