Complete log on; genericize docs (#22)

Still a WIP, but closing in...
This commit is contained in:
Daniel J. Summers 2021-09-06 15:42:15 -04:00
parent 4e84bc251a
commit 989beb0659
19 changed files with 168 additions and 76 deletions

View File

@ -6,7 +6,7 @@ open System.Text.Json.Serialization
/// The variables we need from the account information we get from Mastodon /// The variables we need from the account information we get from Mastodon
[<NoComparison; NoEquality; AllowNullLiteral>] [<NoComparison; NoEquality; AllowNullLiteral>]
type MastodonAccount () = type MastodonAccount () =
/// The user name (what we store as naUser) /// The user name (what we store as mastodonUser)
[<JsonPropertyName "username">] [<JsonPropertyName "username">]
member val Username = "" with get, set member val Username = "" with get, set
/// The account name; will generally be the same as username for local accounts, which is all we can verify /// The account name; will generally be the same as username for local accounts, which is all we can verify

View File

@ -6,6 +6,7 @@ open JobsJobsJobs.Domain.Types
open Polly open Polly
open RethinkDb.Driver open RethinkDb.Driver
open RethinkDb.Driver.Net open RethinkDb.Driver.Net
open RethinkDb.Driver.Ast
/// Shorthand for the RethinkDB R variable (how every command starts) /// Shorthand for the RethinkDB R variable (how every command starts)
let private r = RethinkDB.R let private r = RethinkDB.R
@ -166,10 +167,20 @@ module Startup =
log.LogInformation $"Creating \"{idx}\" index on {table}" log.LogInformation $"Creating \"{idx}\" index on {table}"
r.Table(table).IndexCreate(idx).RunWriteAsync conn |> awaitIgnore) r.Table(table).IndexCreate(idx).RunWriteAsync conn |> awaitIgnore)
} }
do! ensureIndexes Table.Citizen [ "naUser" ]
do! ensureIndexes Table.Listing [ "citizenId"; "continentId"; "isExpired" ] do! ensureIndexes Table.Listing [ "citizenId"; "continentId"; "isExpired" ]
do! ensureIndexes Table.Profile [ "continentId" ] do! ensureIndexes Table.Profile [ "continentId" ]
do! ensureIndexes Table.Success [ "citizenId" ] do! ensureIndexes Table.Success [ "citizenId" ]
// The instance/user is a compound index
let! userIdx = r.Table(Table.Citizen).IndexList().RunResultAsync<string list> conn
match userIdx |> List.contains "instanceUser" with
| true -> ()
| false ->
let! _ =
r.Table(Table.Citizen)
.IndexCreate("instanceUser",
ReqlFunction1 (fun row -> upcast r.Array (row.G "instance", row.G "mastodonUser")))
.RunWriteAsync conn
()
} }
@ -215,7 +226,6 @@ let regexContains = System.Text.RegularExpressions.Regex.Escape >> sprintf "(?i)
open JobsJobsJobs.Domain open JobsJobsJobs.Domain
open JobsJobsJobs.Domain.SharedTypes open JobsJobsJobs.Domain.SharedTypes
open RethinkDb.Driver.Ast
/// Profile data access functions /// Profile data access functions
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
@ -287,7 +297,7 @@ module Profile =
.HashMap("displayName", .HashMap("displayName",
r.Branch (it.G("realName" ).Default_("").Ne "", it.G "realName", r.Branch (it.G("realName" ).Default_("").Ne "", it.G "realName",
it.G("displayName").Default_("").Ne "", it.G "displayName", it.G("displayName").Default_("").Ne "", it.G "displayName",
it.G "naUser")) it.G "mastodonUser"))
.With ("citizenId", it.G "id"))) .With ("citizenId", it.G "id")))
.Pluck("citizenId", "displayName", "seekingEmployment", "remoteWork", "fullTime", "lastUpdatedOn") .Pluck("citizenId", "displayName", "seekingEmployment", "remoteWork", "fullTime", "lastUpdatedOn")
.OrderBy(ReqlFunction1 (fun it -> upcast it.G("displayName").Downcase ())) .OrderBy(ReqlFunction1 (fun it -> upcast it.G("displayName").Downcase ()))
@ -348,10 +358,10 @@ module Citizen =
.RunResultAsync<Citizen> .RunResultAsync<Citizen>
|> withReconnOption conn |> withReconnOption conn
/// Find a citizen by their No Agenda Social username /// Find a citizen by their Mastodon username
let findByNaUser (naUser : string) conn = let findByMastodonUser (instance : string) (mastodonUser : string) conn =
r.Table(Table.Citizen) r.Table(Table.Citizen)
.GetAll(naUser).OptArg("index", "naUser").Nth(0) .GetAll(r.Array (instance, mastodonUser)).OptArg("index", "instanceUser").Nth(0)
.RunResultAsync<Citizen> .RunResultAsync<Citizen>
|> withReconnOption conn |> withReconnOption conn
@ -546,9 +556,17 @@ module Success =
.HashMap("citizenName", .HashMap("citizenName",
r.Branch(it.G("realName" ).Default_("").Ne "", it.G "realName", r.Branch(it.G("realName" ).Default_("").Ne "", it.G "realName",
it.G("displayName").Default_("").Ne "", it.G "displayName", it.G("displayName").Default_("").Ne "", it.G "displayName",
it.G "naUser")) it.G "mastodonUser"))
.With ("hasStory", it.G("story").Default_("").Gt ""))) .With ("hasStory", it.G("story").Default_("").Gt "")))
.Pluck("id", "citizenId", "citizenName", "recordedOn", "fromHere", "hasStory") .Pluck("id", "citizenId", "citizenName", "recordedOn", "fromHere", "hasStory")
.OrderBy(r.Desc "recordedOn") .OrderBy(r.Desc "recordedOn")
.RunResultAsync<StoryEntry list> .RunResultAsync<StoryEntry list>
|> withReconn conn |> withReconn conn
(*
-- To replace naUser with mastodonUser for NAS --
r.db('jobsjobsjobs').table('citizen').replace(function(it) {
return it.merge({ mastodonUser: it('naUser'), instance: 'nas' }).without('naUser');
})
*)

View File

@ -117,22 +117,23 @@ module Citizen =
| Some instance -> | Some instance ->
let log = (logger ctx).CreateLogger (nameof JobsJobsJobs.Api.Auth) let log = (logger ctx).CreateLogger (nameof JobsJobsJobs.Api.Auth)
match! Auth.verifyWithMastodon authCode instance cfg.ReturnUrl log with match! Auth.verifyWithMastodon authCode instance cfg.ReturnHost log with
| Ok account -> | Ok account ->
// Step 2 - Find / establish Jobs, Jobs, Jobs account // Step 2 - Find / establish Jobs, Jobs, Jobs account
let now = (clock ctx).GetCurrentInstant () let now = (clock ctx).GetCurrentInstant ()
let dbConn = conn ctx let dbConn = conn ctx
let! citizen = task { let! citizen = task {
match! Data.Citizen.findByNaUser account.Username dbConn with match! Data.Citizen.findByMastodonUser instance.Abbr account.Username dbConn with
| None -> | None ->
let it : Citizen = let it : Citizen =
{ id = CitizenId.create () { id = CitizenId.create ()
naUser = account.Username instance = instance.Abbr
displayName = noneIfEmpty account.DisplayName mastodonUser = account.Username
realName = None displayName = noneIfEmpty account.DisplayName
profileUrl = account.Url realName = None
joinedOn = now profileUrl = account.Url
lastSeenOn = now joinedOn = now
lastSeenOn = now
} }
do! Data.Citizen.add it dbConn do! Data.Citizen.add it dbConn
return it return it

View File

@ -1,5 +1,7 @@
{ {
"Rethink": { "Rethink": {
"Hostname": "data02.bitbadger.solutions",
"Db": "jobsjobsjobs-dev"
}, },
"Auth": { "Auth": {
"ReturnHost": "http://localhost:5000", "ReturnHost": "http://localhost:5000",

View File

@ -46,7 +46,7 @@ export function yesOrNo (cond : boolean) : string {
* @returns The citizen's display name * @returns The citizen's display name
*/ */
export function citizenName (cit : Citizen) : string { export function citizenName (cit : Citizen) : string {
return cit.realName ?? cit.displayName ?? cit.naUser return cit.realName ?? cit.displayName ?? cit.mastodonUser
} }
</script> </script>

View File

@ -3,8 +3,10 @@
export interface Citizen { export interface Citizen {
/** The ID of the user */ /** The ID of the user */
id : string id : string
/** The abbreviation of the instance where this citizen is based */
instance : string
/** The handle by which the user is known on Mastodon */ /** The handle by which the user is known on Mastodon */
naUser : string mastodonUser : string
/** The user's display name from Mastodon (updated every login) */ /** The user's display name from Mastodon (updated every login) */
displayName : string | undefined displayName : string | undefined
/** The user's real name */ /** The user's real name */

View File

@ -121,7 +121,7 @@ const routes: Array<RouteRecordRaw> = [
component: () => import(/* webpackChunkName: "so-long" */ "../views/so-long/DeletionOptions.vue") component: () => import(/* webpackChunkName: "so-long" */ "../views/so-long/DeletionOptions.vue")
}, },
{ {
path: "/so-long/success", path: "/so-long/success/:url",
name: "DeletionSuccess", name: "DeletionSuccess",
component: () => import(/* webpackChunkName: "so-long" */ "../views/so-long/DeletionSuccess.vue") component: () => import(/* webpackChunkName: "so-long" */ "../views/so-long/DeletionSuccess.vue")
}, },

View File

@ -24,7 +24,7 @@ export default createStore({
state: () : State => { state: () : State => {
return { return {
user: undefined, user: undefined,
logOnState: "<em>Welcome back! Verifying your No Agenda Social account&hellip;</em>", logOnState: "<em>Welcome back!</em>",
continents: [] continents: []
} }
}, },

View File

@ -21,8 +21,8 @@ article
p. p.
Clicking the #[span.link View] link on a listing brings up the full view page for a listing. This page displays all Clicking the #[span.link View] link on a listing brings up the full view page for a listing. This page displays all
of the information from the search results, along with the citizen who posted it, and the full details of the job. of the information from the search results, along with the citizen who posted it, and the full details of the job.
The citizen&rsquo;s name is a link to their profile page at No Agenda Social; you can use that to get their handle, The citizen&rsquo;s name is a link to their profile page at their Mastodon instance; you can use that to get their
and use NAS&rsquo;s communication facilites to inquire about the position. handle, and use Mastodon&rsquo;s communication facilites to inquire about the position.
p: em.text-muted. p: em.text-muted.
(If you know of a way to construct a link to Mastodon that would start a direct message, please reach out; (If you know of a way to construct a link to Mastodon that would start a direct message, please reach out;
I&rsquo;ve searched and searched, and asked NAS, but have not yet determined how to do that.) I&rsquo;ve searched and searched, and asked NAS, but have not yet determined how to do that.)
@ -43,9 +43,9 @@ article
The #[span.link My Job Listings] page will show you all of your active job listings just below the The #[span.link My Job Listings] page will show you all of your active job listings just below the
#[span.button Add a Job Listing] button. Within this table, you can edit the listing, view it, or expire it (more on #[span.button Add a Job Listing] button. Within this table, you can edit the listing, view it, or expire it (more on
that below). The #[span.link View] link will show you the job listing just as other users will see it. You can share that below). The #[span.link View] link will show you the job listing just as other users will see it. You can share
the link from your browser over on No Agenda Social, and those who click on it will be able to view it. (Existing the link from your browser on any No Agenda-affiliated Mastodon instance, and those who click on it will be able to
users of Jobs, Jobs, Jobs will go right to it; others will need to authorize this site&rsquo;s access, but then they view it. (Existing users of Jobs, Jobs, Jobs will go right to it; others will need to authorize this site&rsquo;s
will get there as well.) access, but then they will get there as well.)
h5 Expire a Job Listing h5 Expire a Job Listing
p. p.
@ -68,7 +68,7 @@ article
The #[span.link Employment Profiles] link at the side allows you to search for profiles by continent, the The #[span.link Employment Profiles] link at the side allows you to search for profiles by continent, the
citizen&rsquo;s desire for remote work, a skill, or any text in their professional biography and experience. If you citizen&rsquo;s desire for remote work, a skill, or any text in their professional biography and experience. If you
find someone with whom you&rsquo;d like to discuss potential opportunities, the name at the top of the profile links find someone with whom you&rsquo;d like to discuss potential opportunities, the name at the top of the profile links
to their No Agenda Social account, where you can use its features to get in touch. to their Mastodon profile, where you can use its features to get in touch.
hr hr
@ -76,8 +76,8 @@ article
p. p.
The employment profile is your r&eacute;sum&eacute;, visible to other citizens here. It also allows you to specify The employment profile is your r&eacute;sum&eacute;, visible to other citizens here. It also allows you to specify
your real name, if you so desire; if that is filled in, that is how you will be identified in search results, your real name, if you so desire; if that is filled in, that is how you will be identified in search results,
profile views, etc. If not, you will be identified as you are on No Agenda Social; this system updates your current profile views, etc. If not, you will be identified as you are on your Mastodon instance; this system updates your
display name each time you log on. current display name each time you log on.
h5 Completing Your Profile h5 Completing Your Profile
p. p.
@ -99,19 +99,19 @@ article
li. li.
If you check the #[span.link Allow my profile to be searched publicly] checkbox #[strong and] you are seeking If you check the #[span.link Allow my profile to be searched publicly] checkbox #[strong and] you are seeking
employment, your continent, region, and skills fields will be searchable and displayed to public users of the employment, your continent, region, and skills fields will be searchable and displayed to public users of the
site. They will not be tied to your No Agenda Social handle or real name; they are there to let people peek site. They will not be tied to your Mastodon handle or real name; they are there to let people peek behind the
behind the curtain a bit, and hopefully inspire them to join us. curtain a bit, and hopefully inspire them to join us.
h5 Viewing and Sharing Your Profile h5 Viewing and Sharing Your Profile
p. p.
Once your profile has been established, the #[span.link My Employment Profile] page will have a button at the bottom Once your profile has been established, the #[span.link My Employment Profile] page will have a button at the bottom
that will let you view your profile the way all other validated users will be able to see it. (There will also be a that will let you view your profile the way all other validated users will be able to see it. (There will also be a
link to this page from the #[span.link Dashboard].) The URL of this page can be shared on No Agenda Social, if you link to this page from the #[span.link Dashboard].) The URL of this page can be shared on any No Agenda-affiliated
would like to share it there. Just as with job listings, existing users will go straight there, while other No Mastodon instance, if you would like to share it there. Just as with job listings, existing users will go straight
Agenda Social users will get there once they authorize this application. there, while others will get there once they authorize this application.
p. p.
The name on employment profiles is a link to that user&rsquo;s profile on No Agenda Social; from there, others can The name on employment profiles is a link to that user&rsquo;s profile on their Mastodon instance; from there,
communicate further with you using the tools Mastodon provides. others can communicate further with you using the tools Mastodon provides.
h5 &ldquo;I Found a Job!&rdquo; h5 &ldquo;I Found a Job!&rdquo;
p. p.

View File

@ -2,7 +2,7 @@
article article
page-title(title="Privacy Policy") page-title(title="Privacy Policy")
h3 Privacy Policy h3 Privacy Policy
p: em (as of February 6#[sup th], 2021) p: em (as of September 6#[sup th], 2021)
p. p.
{{name}} (&ldquo;we,&rdquo; &ldquo;our,&rdquo; or &ldquo;us&rdquo;) is committed to protecting your privacy. This {{name}} (&ldquo;we,&rdquo; &ldquo;our,&rdquo; or &ldquo;us&rdquo;) is committed to protecting your privacy. This
@ -58,7 +58,7 @@ article
li Name / Username li Name / Username
li Coarse Geographic Location li Coarse Geographic Location
li Employment History li Employment History
li No Agenda Social Account Name / Profile li Mastodon Account Name / Profile
h4 How Do We Use The Information We Collect? h4 How Do We Use The Information We Collect?
p Any of the information we collect from you may be used in one of the following ways: p Any of the information we collect from you may be used in one of the following ways:
@ -75,9 +75,9 @@ article
p {{name}} will collect End User Data necessary to provide the {{name}} services to our customers. p {{name}} will collect End User Data necessary to provide the {{name}} services to our customers.
p. p.
End users may voluntarily provide us with information they have made available on social media websites End users may voluntarily provide us with information they have made available on social media websites
(specifically No Agenda Social). If you provide us with any such information, we may collect publicly available (specifically No Agenda-affiliated Mastodon instances). If you provide us with any such information, we may collect
information from the social media websites you have indicated. You can control how much of your information social publicly available information from the social media websites you have indicated. You can control how much of your
media websites make public by visiting these websites and changing your privacy settings. information social media websites make public by visiting these websites and changing your privacy settings.
h4 When does {{name}} use customer information from third parties? h4 When does {{name}} use customer information from third parties?
p We do not utilize third party information apart from the end-user data described above. p We do not utilize third party information apart from the end-user data described above.
@ -223,10 +223,10 @@ article
h4 Tracking Technologies h4 Tracking Technologies
p. p.
{{name}} does not use any tracking technologies. When an authorization code is received from No Agenda Social, that {{name}} does not use any tracking technologies. When an authorization code is received from Mastodon, that token is
token is stored in the browser&rsquo;s memory, and the Service uses tokens on each request for data. If the page is stored in the browser&rsquo;s memory, and the Service uses tokens on each request for data. If the page is refreshed
refreshed or the browser window/tab is closed, this token disappears, and a new one must be generated before the or the browser window/tab is closed, this token disappears, and a new one must be generated before the application
application can be used again. can be used again.
h4 Information about General Data Protection Regulation (GDPR) h4 Information about General Data Protection Regulation (GDPR)
p. p.
@ -335,6 +335,12 @@ article
h4 Contact Us h4 Contact Us
p Don&rsquo;t hesitate to contact us if you have any questions. p Don&rsquo;t hesitate to contact us if you have any questions.
ul: li Via this Link: #[router-link(to="/how-it-works") https://noagendacareers.com/how-it-works] ul: li Via this Link: #[router-link(to="/how-it-works") https://noagendacareers.com/how-it-works]
hr
p: em.
Change on September 6#[sup th], 2021 &ndash; replaced &ldquo;No Agenda Social&rdquo; with generic terms for any
authorized Mastodon instance.
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@ -2,7 +2,7 @@
article article
page-title(title="Terms of Service") page-title(title="Terms of Service")
h3 Terms of Service h3 Terms of Service
p: em (as of February 6#[sup th], 2021) p: em (as of September 6#[sup th], 2021)
h4 Acceptance of Terms h4 Acceptance of Terms
p. p.
@ -11,12 +11,20 @@ article
acceptance of these terms. acceptance of these terms.
h4 Description of Service and Registration h4 Description of Service and Registration
p. p
Jobs, Jobs, Jobs is a service that allows individuals to enter and amend employment profiles, restricting access | 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
#[a(href="https://noagendasocial.com" target="_blank") No Agenda Social]. Registration is accomplished by allowing = " "
Jobs, Jobs, Jobs to read one&rsquo;s No Agenda Social profile. See our template(v-for="(it, idx) in instances" :key="idx")
#[router-link(to="/privacy-policy") privacy policy] for details on the personal (user) information we maintain. 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&rsquo;s Mastodon profile. See our
= " "
router-link(to="/privacy-policy") privacy policy
= " "
| for details on the personal (user) information we maintain.
h4 Liability h4 Liability
p. p.
@ -34,4 +42,30 @@ article
p. p.
You may also wish to review our #[router-link(to="/privacy-policy") privacy policy] to learn how we handle your You may also wish to review our #[router-link(to="/privacy-policy") privacy policy] to learn how we handle your
data. data.
hr
p: em.
Change on September 6#[sup th], 2021 &ndash; replaced &ldquo;No Agenda Social&rdquo; with a list of all No
Agenda-affiliated Mastodon instances.
</template> </template>
<script setup lang="ts">
import { onMounted, Ref, ref } from "vue"
import api, { Instance } from "@/api"
import { toastError } from "@/components/layout/AppToaster.vue"
const instances : Ref<Instance[]> = ref([])
onMounted(async () => {
const apiResp = await api.instances.all()
if (typeof apiResp === "string") {
toastError(apiResp, "retrieving instances")
} else if (typeof apiResp === "undefined") {
toastError("No instances to display", undefined)
} else {
instances.value = apiResp
}
})
</script>

View File

@ -30,6 +30,7 @@ const logOn = async () => {
} else if (typeof instance === "undefined") { } else if (typeof instance === "undefined") {
setMessage(`Mastodon instance ${abbr} not found`) setMessage(`Mastodon instance ${abbr} not found`)
} else { } else {
setMessage(`<em>Welcome back! Verifying your ${instance.name} account&hellip;</em>`)
const code = route.query.code const code = route.query.code
if (code) { if (code) {
await store.dispatch("logOn", { abbr, code }) await store.dispatch("logOn", { abbr, code })

View File

@ -24,7 +24,6 @@ const selected : Ref<Instance | undefined> = ref(undefined)
/** The authorization URL to which the user should be directed */ /** The authorization URL to which the user should be directed */
const authUrl = computed(() => { const authUrl = computed(() => {
if (selected.value) { if (selected.value) {
/** The client ID for Jobs, Jobs, Jobs at No Agenda Social */
const client = `client_id=${selected.value.clientId}` const client = `client_id=${selected.value.clientId}`
const scope = "scope=read:accounts" const scope = "scope=read:accounts"
const redirect = `redirect_uri=${document.location.origin}/citizen/${selected.value.abbr}/authorized` const redirect = `redirect_uri=${document.location.origin}/citizen/${selected.value.abbr}/authorized`
@ -35,7 +34,7 @@ const authUrl = computed(() => {
}) })
/** /**
* Select a given Mastadon instance * Select a given Mastodon instance
* *
* @param abbr The abbreviation of the instance being selected * @param abbr The abbreviation of the instance being selected
*/ */

View File

@ -66,7 +66,7 @@ const title = computed(() => it.value ? `${it.value.listing.title} | Job Listing
const details = computed(() => toHtml(it.value?.listing.text ?? "")) const details = computed(() => toHtml(it.value?.listing.text ?? ""))
/** The NAS profile URL for the citizen who posted this job listing */ /** The NAS profile URL for the citizen who posted this job listing */
const profileUrl = computed(() => citizen.value ? `https://noagendasocial.com/@${citizen.value.naUser}` : "") const profileUrl = computed(() => citizen.value ? citizen.value.profileUrl : "")
/** The needed by date, formatted in SHOUTING MODE */ /** The needed by date, formatted in SHOUTING MODE */
const neededBy = (nb : string) => formatNeededBy(nb).toUpperCase() const neededBy = (nb : string) => formatNeededBy(nb).toUpperCase()

View File

@ -13,10 +13,10 @@ article
p. p.
This option will make it like you never visited this site. It will delete your profile, skills, success stories, and This option will make it like you never visited this site. It will delete your profile, skills, success stories, and
account. This is what you want to use if you want to disappear from this application. Clicking the button below account. This is what you want to use if you want to disappear from this application. Clicking the button below
#[strong will not] affect your No Agenda Social account in any way; its effects are limited to Jobs, Jobs, Jobs. #[strong will not] affect your Mastodon account in any way; its effects are limited to Jobs, Jobs, Jobs.
p: em. p: em.
(This will not revoke this application&rsquo;s permissions on No Agenda Social; you will have to remove this (This will not revoke this application&rsquo;s permissions on Mastodon; you will have to remove this yourself. The
yourself. The confirmation message has a link where you can do this; once the page loads, find the confirmation message has a link where you can do this; once the page loads, find the
#[strong Jobs, Jobs, Jobs] entry, and click the #[strong &times; Revoke] link for that entry.) #[strong Jobs, Jobs, Jobs] entry, and click the #[strong &times; Revoke] link for that entry.)
p.text-center: button.btn.btn-danger(@click.prevent="deleteAccount") Delete Your Entire Account p.text-center: button.btn.btn-danger(@click.prevent="deleteAccount") Delete Your Entire Account
</template> </template>
@ -32,9 +32,12 @@ import { useStore } from "@/store"
const store = useStore() const store = useStore()
const router = useRouter() const router = useRouter()
/** The currently logged-on user */
const user = store.state.user as LogOnSuccess
/** Delete the profile only; redirect to home page on success */ /** Delete the profile only; redirect to home page on success */
const deleteProfile = async () => { const deleteProfile = async () => {
const resp = await api.profile.delete(store.state.user as LogOnSuccess) const resp = await api.profile.delete(user)
if (typeof resp === "string") { if (typeof resp === "string") {
toastError(resp, "Deleting Profile") toastError(resp, "Deleting Profile")
} else { } else {
@ -45,13 +48,27 @@ const deleteProfile = async () => {
/** Delete everything pertaining to the user's account */ /** Delete everything pertaining to the user's account */
const deleteAccount = async () => { const deleteAccount = async () => {
const resp = await api.citizen.delete(store.state.user as LogOnSuccess) const citizenResp = await api.citizen.retrieve(user.citizenId, user)
if (typeof resp === "string") { if (typeof citizenResp === "string") {
toastError(resp, "Deleting Account") toastError(citizenResp, "retrieving citizen")
} else if (typeof citizenResp === "undefined") {
toastError("Could not retrieve citizen record", undefined)
} else { } else {
store.commit("clearUser") const instResp = await api.instances.byAbbr(citizenResp.instance)
toastSuccess("Account Deleted Successfully") if (typeof instResp === "string") {
router.push("/so-long/success") toastError(instResp, "retriving instance")
} else if (typeof instResp === "undefined") {
toastError("Could not retrieve instance", undefined)
} else {
const resp = await api.citizen.delete(user)
if (typeof resp === "string") {
toastError(resp, "Deleting Account")
} else {
store.commit("clearUser")
toastSuccess("Account Deleted Successfully")
router.push(`/so-long/success/${instResp.url}`)
}
}
} }
} }
</script> </script>

View File

@ -4,8 +4,18 @@ article
h3.pb-3 Account Deletion Success h3.pb-3 Account Deletion Success
p. p.
Your account has been successfully deleted. To revoke the permissions you have previously granted to this Your account has been successfully deleted. To revoke the permissions you have previously granted to this
application, find it in #[a(href="https://noagendasocial.com/oauth/authorized_applications") this list] and click application, find it in #[a(:href="`${url}/oauth/authorized_applications`") this list] and click
#[strong &times; Revoke]. Otherwise, clicking &ldquo;Log On&rdquo; in the left-hand menu will create a new, empty #[strong &times; Revoke]. Otherwise, clicking &ldquo;Log On&rdquo; in the left-hand menu will create a new, empty
account without prompting you further. account without prompting you further.
p Thank you for participating, and thank you for your courage. #GitmoNation p Thank you for participating, and thank you for your courage. #GitmoNation
</template> </template>
<script setup lang="ts">
import { useRoute } from "vue-router"
const route = useRoute()
/** The URL of the instance from which the deleted user had authorized access */
const url = route.params.url as string
</script>

View File

@ -33,7 +33,7 @@ module CitizenId =
module Citizen = module Citizen =
/// Get the name of the citizen (the first of real name, display name, or handle that is filled in) /// Get the name of the citizen (the first of real name, display name, or handle that is filled in)
let name x = let name x =
[ x.realName; x.displayName; Some x.naUser ] [ x.realName; x.displayName; Some x.mastodonUser ]
|> List.find Option.isSome |> List.find Option.isSome
|> Option.get |> Option.get

View File

@ -92,8 +92,8 @@ type MastodonInstance () =
/// The authorization options for Jobs, Jobs, Jobs /// The authorization options for Jobs, Jobs, Jobs
type AuthOptions () = type AuthOptions () =
/// The return URL for Mastodoon verification /// The host for the return URL for Mastodoon verification
member val ReturnUrl = "" with get, set member val ReturnHost = "" with get, set
/// The secret with which the server signs the JWTs for auth once we've verified with Mastodon /// The secret with which the server signs the JWTs for auth once we've verified with Mastodon
member val ServerSecret = "" with get, set member val ServerSecret = "" with get, set
/// The instances configured for use /// The instances configured for use

View File

@ -13,19 +13,21 @@ type CitizenId = CitizenId of Guid
[<CLIMutable; NoComparison; NoEquality>] [<CLIMutable; NoComparison; NoEquality>]
type Citizen = { type Citizen = {
/// The ID of the user /// The ID of the user
id : CitizenId id : CitizenId
/// The Mastodon instance abbreviation from which this citizen is authorized
instance : string
/// The handle by which the user is known on Mastodon /// The handle by which the user is known on Mastodon
naUser : string mastodonUser : string
/// The user's display name from Mastodon (updated every login) /// The user's display name from Mastodon (updated every login)
displayName : string option displayName : string option
/// The user's real name /// The user's real name
realName : string option realName : string option
/// The URL for the user's Mastodon profile /// The URL for the user's Mastodon profile
profileUrl : string profileUrl : string
/// When the user joined Jobs, Jobs, Jobs /// When the user joined Jobs, Jobs, Jobs
joinedOn : Instant joinedOn : Instant
/// When the user last logged in /// When the user last logged in
lastSeenOn : Instant lastSeenOn : Instant
} }