diff --git a/src/JobsJobsJobs/Api/Data.fs b/src/JobsJobsJobs/Api/Data.fs index a46793a..3ab7ec8 100644 --- a/src/JobsJobsJobs/Api/Data.fs +++ b/src/JobsJobsJobs/Api/Data.fs @@ -360,10 +360,14 @@ module Citizen = /// Find a citizen by their Mastodon username let findByMastodonUser (instance : string) (mastodonUser : string) conn = - r.Table(Table.Citizen) - .GetAll(r.Array (instance, mastodonUser)).OptArg("index", "instanceUser").Nth(0) - .RunResultAsync - |> withReconnOption conn + fun c -> task { + let! u = + r.Table(Table.Citizen) + .GetAll(r.Array (instance, mastodonUser)).OptArg("index", "instanceUser").Limit(1) + .RunResultAsync c + return u |> List.tryHead + } + |> withReconn conn /// Add a citizen let add (citizen : Citizen) conn = diff --git a/src/JobsJobsJobs/Api/Handlers.fs b/src/JobsJobsJobs/Api/Handlers.fs index f32a847..6e877dd 100644 --- a/src/JobsJobsJobs/Api/Handlers.fs +++ b/src/JobsJobsJobs/Api/Handlers.fs @@ -201,14 +201,6 @@ module Instances = fun next ctx -> task { return! json ((authConfig ctx).Instances |> Array.map toInstance) next ctx } - - // GET: /api/instance/[abbr] - let byAbbr abbr : HttpHandler = - fun next ctx -> task { - match (authConfig ctx).Instances |> Array.tryFind (fun it -> it.Abbr = abbr) with - | Some inst -> return! json (toInstance inst) next ctx - | None -> return! Error.notFound next ctx - } /// Handlers for /api/listing[s] routes @@ -530,12 +522,7 @@ let allEndpoints = [ DELETE [ route "" Citizen.delete ] ] GET_HEAD [ route "/continents" Continent.all ] - subRoute "/instance" [ - GET_HEAD [ - route "s" Instances.all - routef "/%s" Instances.byAbbr - ] - ] + GET_HEAD [ route "/instances" Instances.all ] subRoute "/listing" [ GET_HEAD [ routef "/%O" Listing.get diff --git a/src/JobsJobsJobs/App/package.json b/src/JobsJobsJobs/App/package.json index ff9210a..4901cfd 100644 --- a/src/JobsJobsJobs/App/package.json +++ b/src/JobsJobsJobs/App/package.json @@ -1,12 +1,13 @@ { "name": "jobs-jobs-jobs", - "version": "2.0.0", + "version": "2.1.0", "private": true, "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint", - "apiserve": "vue-cli-service build && cd ../Api && dotnet run -c Debug" + "apiserve": "vue-cli-service build && cd ../Api && dotnet run -c Debug", + "publish": "vue-cli-service build --modern && cd ../Api && dotnet publish -c Release -r linux-x64 --self-contained false" }, "dependencies": { "@mdi/js": "^5.9.55", diff --git a/src/JobsJobsJobs/App/src/App.vue b/src/JobsJobsJobs/App/src/App.vue index e112e60..90b0321 100644 --- a/src/JobsJobsJobs/App/src/App.vue +++ b/src/JobsJobsJobs/App/src/App.vue @@ -40,7 +40,7 @@ export function yesOrNo (cond : boolean) : string { } /** - * Get the display name for a citizen (the first available among real, display, or NAS handle) + * Get the display name for a citizen (the first available among real, display, or Mastodon handle) * * @param cit The citizen * @returns The citizen's display name diff --git a/src/JobsJobsJobs/App/src/api/index.ts b/src/JobsJobsJobs/App/src/api/index.ts index a0374da..ee1971e 100644 --- a/src/JobsJobsJobs/App/src/api/index.ts +++ b/src/JobsJobsJobs/App/src/api/index.ts @@ -152,16 +152,7 @@ export default { * @returns All instances, or an error */ all: async () : Promise => - apiResult(await fetch(apiUrl("instances"), { method: "GET" }), "retrieving Mastodon instances"), - - /** - * Retrieve a Mastodon instance by its abbreviation - * - * @param abbr The abbreviation of the Mastodon instance to retrieve - * @returns The Mastodon instance (if found), undefined (if not found), or an error string - */ - byAbbr: async (abbr : string) : Promise => - apiResult(await fetch(apiUrl(`instance/${abbr}`), { method: "GET" }), "retrieving Mastodon instance") + apiResult(await fetch(apiUrl("instances"), { method: "GET" }), "retrieving Mastodon instances") }, /** API functions for job listings */ diff --git a/src/JobsJobsJobs/App/src/router/index.ts b/src/JobsJobsJobs/App/src/router/index.ts index e830ebb..1814410 100644 --- a/src/JobsJobsJobs/App/src/router/index.ts +++ b/src/JobsJobsJobs/App/src/router/index.ts @@ -10,7 +10,7 @@ import store from "@/store" import Home from "@/views/Home.vue" import LogOn from "@/views/citizen/LogOn.vue" -/** The URL to which the user should be pointed once they have authorized with NAS */ +/** The URL to which the user should be pointed once they have authorized with Mastodon */ export const AFTER_LOG_ON_URL = "jjj-after-log-on-url" /** @@ -121,7 +121,7 @@ const routes: Array = [ component: () => import(/* webpackChunkName: "so-long" */ "../views/so-long/DeletionOptions.vue") }, { - path: "/so-long/success/:url", + path: "/so-long/success/:abbr", name: "DeletionSuccess", component: () => import(/* webpackChunkName: "so-long" */ "../views/so-long/DeletionSuccess.vue") }, diff --git a/src/JobsJobsJobs/App/src/store/actions.ts b/src/JobsJobsJobs/App/src/store/actions.ts new file mode 100644 index 0000000..bd5e1f3 --- /dev/null +++ b/src/JobsJobsJobs/App/src/store/actions.ts @@ -0,0 +1,8 @@ +/** Logs a user on to Jobs, Jobs, Jobs */ +export const LogOn = "logOn" + +/** Ensures that the continent list in the state has been populated */ +export const EnsureContinents = "ensureContinents" + +/** Ensures that the Mastodon instance list in the state has been populated */ +export const EnsureInstances = "ensureInstances" diff --git a/src/JobsJobsJobs/App/src/store/index.ts b/src/JobsJobsJobs/App/src/store/index.ts index f3e2bd5..3750b91 100644 --- a/src/JobsJobsJobs/App/src/store/index.ts +++ b/src/JobsJobsJobs/App/src/store/index.ts @@ -1,6 +1,8 @@ import { InjectionKey } from "vue" import { createStore, Store, useStore as baseUseStore } from "vuex" -import api, { Continent, LogOnSuccess } from "../api" +import api, { Continent, Instance, LogOnSuccess } from "../api" +import * as Actions from "./actions" +import * as Mutations from "./mutations" /** The state tracked by the application */ export interface State { @@ -10,6 +12,8 @@ export interface State { logOnState: string /** All continents (use `ensureContinents` action) */ continents: Continent[] + /** All instances (use `ensureInstances` action) */ + instances: Instance[] } /** An injection key to identify this state with Vue */ @@ -25,42 +29,50 @@ export default createStore({ return { user: undefined, logOnState: "Welcome back!", - continents: [] + continents: [], + instances: [] } }, mutations: { - setUser (state, user : LogOnSuccess) { - state.user = user - }, - clearUser (state) { - state.user = undefined - }, - setLogOnState (state, message : string) { - state.logOnState = message - }, - setContinents (state, continents : Continent[]) { - state.continents = continents - } + [Mutations.SetUser]: (state, user : LogOnSuccess) => { state.user = user }, + [Mutations.ClearUser]: (state) => { state.user = undefined }, + [Mutations.SetLogOnState]: (state, message : string) => { state.logOnState = message }, + [Mutations.SetContinents]: (state, continents : Continent[]) => { state.continents = continents }, + [Mutations.SetInstances]: (state, instances : Instance[]) => { state.instances = instances } }, actions: { - async logOn ({ commit }, { abbr, code }) { + [Actions.LogOn]: async ({ commit }, { abbr, code }) => { const logOnResult = await api.citizen.logOn(abbr, code) if (typeof logOnResult === "string") { - commit("setLogOnState", logOnResult) + commit(Mutations.SetLogOnState, logOnResult) } else { - commit("setUser", logOnResult) + commit(Mutations.SetUser, logOnResult) } }, - async ensureContinents ({ state, commit }) { + [Actions.EnsureContinents]: async ({ 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) + commit(Mutations.SetContinents, theSeven) + } + }, + [Actions.EnsureInstances]: async ({ state, commit }) => { + if (state.instances.length > 0) return + const instResp = await api.instances.all() + if (typeof instResp === "string") { + console.error(instResp) + } else if (typeof instResp === "undefined") { + console.error("No instances were found; this should not happen") + } else { + commit(Mutations.SetInstances, instResp) } } }, modules: { } }) + +export * as Actions from "./actions" +export * as Mutations from "./mutations" diff --git a/src/JobsJobsJobs/App/src/store/mutations.ts b/src/JobsJobsJobs/App/src/store/mutations.ts new file mode 100644 index 0000000..fc58676 --- /dev/null +++ b/src/JobsJobsJobs/App/src/store/mutations.ts @@ -0,0 +1,14 @@ +/** Set the logged-on user */ +export const SetUser = "setUser" + +/** Clear the logged-on user */ +export const ClearUser = "clearUser" + +/** Set the status of the current log on action */ +export const SetLogOnState = "setLogOnState" + +/** Set the list of continents */ +export const SetContinents = "setContinents" + +/** Set the list of Mastodon instances */ +export const SetInstances = "setInstances" diff --git a/src/JobsJobsJobs/App/src/views/TermsOfService.vue b/src/JobsJobsJobs/App/src/views/TermsOfService.vue index eed2136..ceff16c 100644 --- a/src/JobsJobsJobs/App/src/views/TermsOfService.vue +++ b/src/JobsJobsJobs/App/src/views/TermsOfService.vue @@ -13,14 +13,13 @@ article h4 Description of Service and Registration p | Jobs, Jobs, Jobs is a service that allows individuals to enter and amend employment profiles, restricting access - | to the details of these profiles to other users of + | to the details of these profiles to other users of No Agenda-afilliated Mastodon sites (currently = " " template(v-for="(it, idx) in instances" :key="idx") a(:href="it.url" target="_blank") {{it.name}} template(v-if="idx + 2 < instances.length")= ", " template(v-else-if="idx + 1 < instances.length")= ", and " - template(v-else)= ". " - | Registration is accomplished by allowing Jobs, Jobs, Jobs to read one’s Mastodon profile. See our + | ). Registration is accomplished by allowing Jobs, Jobs, Jobs to read one’s Mastodon profile. See our = " " router-link(to="/privacy-policy") privacy policy = " " @@ -51,21 +50,14 @@ article diff --git a/src/JobsJobsJobs/App/src/views/citizen/Authorized.vue b/src/JobsJobsJobs/App/src/views/citizen/Authorized.vue index 5ad832e..448f61e 100644 --- a/src/JobsJobsJobs/App/src/views/citizen/Authorized.vue +++ b/src/JobsJobsJobs/App/src/views/citizen/Authorized.vue @@ -8,8 +8,7 @@ article diff --git a/src/JobsJobsJobs/App/src/views/listing/ListingView.vue b/src/JobsJobsJobs/App/src/views/listing/ListingView.vue index 278b8bf..335fb6c 100644 --- a/src/JobsJobsJobs/App/src/views/listing/ListingView.vue +++ b/src/JobsJobsJobs/App/src/views/listing/ListingView.vue @@ -65,7 +65,7 @@ const title = computed(() => it.value ? `${it.value.listing.title} | Job Listing /** The HTML details of the job listing */ const details = computed(() => toHtml(it.value?.listing.text ?? "")) -/** The NAS profile URL for the citizen who posted this job listing */ +/** The Mastodon profile URL for the citizen who posted this job listing */ const profileUrl = computed(() => citizen.value ? citizen.value.profileUrl : "") /** The needed by date, formatted in SHOUTING MODE */ diff --git a/src/JobsJobsJobs/App/src/views/so-long/DeletionOptions.vue b/src/JobsJobsJobs/App/src/views/so-long/DeletionOptions.vue index 5f278ff..ab1763a 100644 --- a/src/JobsJobsJobs/App/src/views/so-long/DeletionOptions.vue +++ b/src/JobsJobsJobs/App/src/views/so-long/DeletionOptions.vue @@ -21,14 +21,14 @@ article p.text-center: button.btn.btn-danger(@click.prevent="deleteAccount") Delete Your Entire Account - +import { useStore, Actions, Mutations } from "@/store" - diff --git a/src/JobsJobsJobs/App/src/views/so-long/DeletionSuccess.vue b/src/JobsJobsJobs/App/src/views/so-long/DeletionSuccess.vue index 3416af1..123df3e 100644 --- a/src/JobsJobsJobs/App/src/views/so-long/DeletionSuccess.vue +++ b/src/JobsJobsJobs/App/src/views/so-long/DeletionSuccess.vue @@ -11,11 +11,19 @@ article diff --git a/src/JobsJobsJobs/App/src/views/success-story/StoryView.vue b/src/JobsJobsJobs/App/src/views/success-story/StoryView.vue index d215f84..5ce3119 100644 --- a/src/JobsJobsJobs/App/src/views/success-story/StoryView.vue +++ b/src/JobsJobsJobs/App/src/views/success-story/StoryView.vue @@ -31,7 +31,7 @@ const user = store.state.user as LogOnSuccess /** The story to be displayed */ const story : Ref = ref(undefined) -/** The citizen's name (real, display, or NAS, whichever is found first) */ +/** The citizen's name (real, display, or Mastodon, whichever is found first) */ const citizenName = ref("") /** Retrieve the success story */