diff --git a/src/Fluff4me.ts b/src/Fluff4me.ts index 58bb79a..5140d1a 100644 --- a/src/Fluff4me.ts +++ b/src/Fluff4me.ts @@ -1,10 +1,18 @@ +/* eslint-disable @typescript-eslint/no-misused-promises */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */ +import { BUTTON_REGISTRY, type IButtonImplementation } from "dev/ButtonRegistry"; import UiEventBus from "ui/UiEventBus"; import Env from "utility/Env"; import popup from "utility/Popup"; +import Session from "utility/Session"; if (location.pathname.startsWith("/auth/")) { if (location.pathname.endsWith("/error")) { const params = new URLSearchParams(location.search); + debugger; localStorage.setItem("Popup-Error-Code", params.get("code") ?? "500"); localStorage.setItem("Popup-Error-Message", params.get("message") ?? "Internal Server Error"); } @@ -49,25 +57,40 @@ export default class Fluff4me { await Session.refresh(); - async function openOAuthPopup (serviceName: string) { - await popup(`${Env.API_ORIGIN}auth/${serviceName.toLowerCase()}/begin`, 600, 900) - .then(() => true).catch(err => { console.warn(err); return false; }); + const createButton = (implementation: IButtonImplementation, ...args: ARGS) => { + const button = document.createElement("button"); + button.textContent = implementation.name; + button.addEventListener("click", async () => { + try { + await implementation.execute(...args); + } catch (err) { + const error = err as Error; + console.warn(`Button ${implementation.name} failed to execute:`, error); + } + }); + return button; + }; + - await Session.refresh(); - } - // TODO fetch services from a new auth API - for (const service of ["GitHub", "Discord"]) { - document.getElementById(service.toLowerCase()) - ?.addEventListener("click", () => void openOAuthPopup(service)); + const OAuthServices = await fetch(`${Env.API_ORIGIN}auth/services`, {}) + .then(response => response.json()); + + for (const service of Object.values(OAuthServices.data) as any[]) { + const oauthButton = document.createElement("button"); + oauthButton.textContent = `OAuth ${service.name}`; + document.body.append(oauthButton); + oauthButton.addEventListener("click", async () => { + await popup(service.url_begin, 600, 900) + .then(() => true).catch(err => { console.warn(err); return false; }); + await Session.refresh(); + }); const unoauthbutton = document.createElement("button"); - unoauthbutton.textContent = `UnOAuth ${service}`; + unoauthbutton.textContent = `UnOAuth ${service.name}`; document.body.append(unoauthbutton); - // eslint-disable-next-line @typescript-eslint/no-misused-promises unoauthbutton.addEventListener("click", async () => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access - const id = Session.getAuthServices()[service.toLowerCase()]?.[0]?.id; + const id = Session.getAuthServices()[service.id]?.[0]?.id; if (id === undefined) return; await fetch(`${Env.API_ORIGIN}auth/remove`, { @@ -77,196 +100,53 @@ export default class Fluff4me { "Accept": "application/json", "Content-Type": "application/json", }, - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment body: JSON.stringify({ id }), }); }); } - const signupbutton = document.createElement("button"); - signupbutton.textContent = "Sign Up"; - document.body.append(signupbutton); - // eslint-disable-next-line @typescript-eslint/no-misused-promises - signupbutton.addEventListener("click", async () => { - await fetch(`${Env.API_ORIGIN}author/create`, { - method: "POST", - credentials: "include", - headers: { - "Content-Type": "application/json", - "Accept": "application/json", - }, - body: JSON.stringify({ - name: "Chiri Vulpes", - vanity: "chiri", - }), - }); - - await Session.refresh(); - }); - - const signupbuttontwo = document.createElement("button"); - signupbuttontwo.textContent = "Sign Up 2"; - document.body.append(signupbuttontwo); - // eslint-disable-next-line @typescript-eslint/no-misused-promises - signupbuttontwo.addEventListener("click", async () => { - await fetch(`${Env.API_ORIGIN}author/create`, { - method: "POST", - credentials: "include", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - name: "Lumina Mystere", - vanity: "lumina", - }), - }); - - await Session.refresh(); - }); - - const resetSessionButton = document.createElement("button"); - resetSessionButton.textContent = "Clear Session"; - document.body.append(resetSessionButton); - // eslint-disable-next-line @typescript-eslint/no-misused-promises - resetSessionButton.addEventListener("click", async () => { - await Session.refresh(await fetch(`${Env.API_ORIGIN}session/reset`, { - method: "POST", - credentials: "include", - headers: { - "Accept": "application/json", - "Content-Type": "application/json", - }, - })); - }); - - const viewprofilebutton = document.createElement("button"); - viewprofilebutton.textContent = "View Profile"; - document.body.append(viewprofilebutton); - // eslint-disable-next-line @typescript-eslint/no-misused-promises - viewprofilebutton.addEventListener("click", async () => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const response = await fetch(`${Env.API_ORIGIN}author/chiri/get`, { - credentials: "include", - }).then(response => response.json()); - console.log(response); - }); - - const updateAuthorButton = document.createElement("button"); - updateAuthorButton.textContent = "Update Author"; - document.body.append(updateAuthorButton); - // eslint-disable-next-line @typescript-eslint/no-misused-promises - updateAuthorButton.addEventListener("click", async () => { - await fetch(`${Env.API_ORIGIN}author/update`, { - method: "POST", - credentials: "include", - headers: { - "Accept": "application/json", - "Content-Type": "application/json", - }, - // body: JSON.stringify({ - // name: "Lumina Mystere", - // description: "if this shows up i did a thing successfully", - // support_link: "https://youlovetosee.it", - // support_message: "pls give me money", - // }), - }); - }); - - const createWorkButton = document.createElement("button"); - createWorkButton.textContent = "Create Work"; - document.body.append(createWorkButton); - // eslint-disable-next-line @typescript-eslint/no-misused-promises - createWorkButton.addEventListener("click", async () => { - await fetch(`${Env.API_ORIGIN}work/create`, { - method: "POST", - credentials: "include", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - name: "Test Work", - description: "woo look a story", - vanity: "a-fancy-story", - }), - }); - }); - - const viewWorkButton = document.createElement("button"); - viewWorkButton.textContent = "View Test Work"; - document.body.append(viewWorkButton); - // eslint-disable-next-line @typescript-eslint/no-misused-promises - viewWorkButton.addEventListener("click", async () => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const response = await fetch(`${Env.API_ORIGIN}work/a-fancy-story/get`, { - credentials: "include", - }).then(response => response.json()); - console.log(response); - }); - - const updateWorkButton = document.createElement("button"); - updateWorkButton.textContent = "Update Test Work"; - document.body.append(updateWorkButton); - // eslint-disable-next-line @typescript-eslint/no-misused-promises - updateWorkButton.addEventListener("click", async () => { - await fetch(`${Env.API_ORIGIN}work/a-fancy-story/get`, { - method: "POST", - credentials: "include", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - name: "Updated Test Work", - description: "if this shows up i did a second thing successfully", - visibility: "Public", - }), - }); - }); - - const deleteWorkButton = document.createElement("button"); - deleteWorkButton.textContent = "Delete Work"; - document.body.append(deleteWorkButton); - // eslint-disable-next-line @typescript-eslint/no-misused-promises - deleteWorkButton.addEventListener("click", async () => { - await fetch(`${Env.API_ORIGIN}work/a-fancy-story/delete`, { - method: "POST", - credentials: "include", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - name: "Updated Test Work", - description: "if this shows up i did a second thing successfully", - visibility: "Public", - }), - }); - }); - + // document.body.append(createButton(BUTTON_REGISTRY.createAuthor, "test author 1", "hi-im-an-author")); + document.body.append(createButton(BUTTON_REGISTRY.clearSession)); + + document.body.append(createButton({ + name: "Create Profile 1", + async execute () { + await BUTTON_REGISTRY.createAuthor.execute("prolific author", "somanystories"); + await BUTTON_REGISTRY.createWork.execute("a debut work", "pretty decent", "debut", "Complete", "Public"); + await BUTTON_REGISTRY.createChapter.execute("chapter 1", "woo look it's prolific author's first story!", "debut", "Public"); + await BUTTON_REGISTRY.createWork.execute("sequel to debut", "wow they wrote a sequel", "sequel", "Ongoing", "Public"); + await BUTTON_REGISTRY.createChapter.execute("the chapters", "pretend there's a story here", "sequel", "Public"); + await BUTTON_REGISTRY.createWork.execute("work in progress", "private test", "wip", "Ongoing", "Private"); + await BUTTON_REGISTRY.createChapter.execute("draft", "it's a rough draft", "wip", "Private"); + }, + })); + + document.body.append(createButton({ + name: "Create Profile 2", + async execute () { + await BUTTON_REGISTRY.createAuthor.execute("single story author", "justonestory"); + await BUTTON_REGISTRY.createWork.execute("one big work", "it's long", "bigstory", "Ongoing", "Public"); + await BUTTON_REGISTRY.createChapter.execute("big story", "start of a long story", "bigstory", "Public"); + await BUTTON_REGISTRY.createChapter.execute("big story 2", "middle of a long story", "bigstory", "Public"); + await BUTTON_REGISTRY.createChapter.execute("big story 3", "still the middle of a long story", "bigstory", "Public"); + await BUTTON_REGISTRY.follow.execute("work", "debut"); + }, + })); + + document.body.append(createButton({ + name: "Create Profile 3", + async execute () { + await BUTTON_REGISTRY.createAuthor.execute("prolific follower", "ifollowpeople"); + await BUTTON_REGISTRY.follow.execute("author", "somanystories"); + await BUTTON_REGISTRY.follow.execute("author", "justonestory"); + await BUTTON_REGISTRY.follow.execute("work", "debut"); + await BUTTON_REGISTRY.follow.execute("work", "sequel"); + await BUTTON_REGISTRY.follow.execute("work", "wip"); + await BUTTON_REGISTRY.follow.execute("work", "bigstory"); + }, + })); } } -namespace Session { - export async function refresh (response?: Response) { - const headers: HeadersInit = { - "Accept": "application/json", - }; - response ??= await fetch(`${Env.API_ORIGIN}session`, { headers, credentials: "include" }); - const stateToken = response.headers.get("State-Token"); - if (stateToken) - localStorage.setItem("State-Token", stateToken); - - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const session = await response.json().catch(() => ({})); - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - localStorage.setItem("Session-Auth-Services", JSON.stringify(session?.data?.authServices ?? {})); - } - - export function getStateToken () { - return localStorage.getItem("State-Token"); - } - export function getAuthServices () { - const authServicesString = localStorage.getItem("Session-Auth-Services"); - return authServicesString && JSON.parse(authServicesString) || {}; - } -} diff --git a/src/dev/ButtonRegistry.ts b/src/dev/ButtonRegistry.ts new file mode 100644 index 0000000..960281d --- /dev/null +++ b/src/dev/ButtonRegistry.ts @@ -0,0 +1,243 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +import Env from "utility/Env"; +import Session from "utility/Session"; + +export interface IButtonImplementation { + name: string; + execute (...args: ARGS): any; +} + +export const BUTTON_REGISTRY = { + createAuthor: { + name: "Create Author", + async execute (name: string, vanity: string) { + await fetch(`${Env.API_ORIGIN}author/create`, { + method: "POST", + credentials: "include", + headers: { + "Content-Type": "application/json", + "Accept": "application/json", + }, + body: JSON.stringify({ + name: name, + vanity: vanity, + }), + }); + await Session.refresh(); + }, + }, + + updateAuthor: { + name: "Update Author", + async execute (name?: string, description?: string, vanity?: string, support_link?: string, support_message?: string) { + await fetch(`${Env.API_ORIGIN}author/update`, { + method: "POST", + credentials: "include", + headers: { + "Accept": "application/json", + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: name, + description: description, + vanity: vanity, + support_link: support_link, + support_message: support_message, + }), + }); + }, + }, + + clearSession: { + name: "Clear Session", + async execute () { + await Session.refresh(await fetch(`${Env.API_ORIGIN}session/reset`, { + method: "POST", + credentials: "include", + headers: { + "Accept": "application/json", + "Content-Type": "application/json", + }, + })); + }, + }, + + createWork: { + name: "Create Work", + async execute (name: string, description: string, vanity: string, status?: string, visibility?: string) { + await fetch(`${Env.API_ORIGIN}work/create`, { + method: "POST", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: name, + description: description, + vanity: vanity, + status: status, + visibility: visibility, + }), + }); + }, + }, + + updateWork: { + name: "Update Work", + async execute (url: string, name?: string, description?: string, vanity?: string, status?: string, visibility?: string) { + await fetch(`${Env.API_ORIGIN}work/${url}/update`, { + method: "POST", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: name, + description: description, + vanity: vanity, + status: status, + visibility: visibility, + }), + }); + }, + }, + + deleteWork: { + name: "Delete Work", + async execute (url: string) { + await fetch(`${Env.API_ORIGIN}work/${url}/delete`, { + method: "POST", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }); + }, + }, + + viewWork: { + name: "View Work", + async execute (url: string) { + const response = await fetch(`${Env.API_ORIGIN}work/${url}/get`, { + credentials: "include", + }).then(response => response.json()); + console.log(response); + }, + }, + + createChapter: { + name: "Create Chapter", + async execute (name: string, body: string, work_url: string, visibility?: string) { + await fetch(`${Env.API_ORIGIN}work/${work_url}/chapter/create`, { + method: "POST", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: name, + body: body, + visibility: visibility, + }), + }); + }, + }, + + updateChapter: { + name: "Update Chapter", + async execute (name?: string, body?: string, visibility?: string) { + await fetch(`${Env.API_ORIGIN}work/a-fancy-story/chapter/1/update`, { + method: "POST", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name, + body, + visibility, + }), + }); + }, + }, + + deleteChapter: { + name: "Delete Chapter", + async execute (work_url: string, index: string) { + await fetch(`${Env.API_ORIGIN}work/${work_url}/chapter/${index}/delete`, { + method: "POST", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }); + }, + }, + + viewChapter: { + name: "View Chapter", + async execute (work_url: string, index: string) { + const response = await fetch(`${Env.API_ORIGIN}work/${work_url}/chapter/${index}/get`, { + credentials: "include", + }).then(response => response.json()); + console.log(response); + }, + }, + + follow: { + name: "Follow", + async execute (type: string, vanity: string) { + await fetch(`${Env.API_ORIGIN}follow/${type}/${vanity}`, { + method: "POST", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }); + }, + }, + + unfollow: { + name: "Unfollow", + async execute (type: string, vanity: string) { + await fetch(`${Env.API_ORIGIN}unfollow/${type}/${vanity}`, { + method: "POST", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }); + }, + }, + + getFollow: { + name: "Get Follow", + async execute (type: string, vanity: string) { + const response = await fetch(`${Env.API_ORIGIN}follows/${type}/${vanity}`, { + credentials: "include", + }).then(response => response.json()); + console.log(response); + }, + }, + + getAllFollows: { + name: "Get All Follows", + async execute (type: string) { + const response = await fetch(`${Env.API_ORIGIN}following/${type}`, { + credentials: "include", + }).then(response => response.json()); + console.log(response); + }, + }, + + getAllFollowsMerged: { + name: "Get All Follows Merged", + async execute () { + const response = await fetch(`${Env.API_ORIGIN}following`, { + credentials: "include", + }).then(response => response.json()); + console.log(response); + }, + }, + +} satisfies Record>; diff --git a/src/utility/Session.ts b/src/utility/Session.ts new file mode 100644 index 0000000..3ace4d4 --- /dev/null +++ b/src/utility/Session.ts @@ -0,0 +1,29 @@ +import Env from "utility/Env"; + +namespace Session { + export async function refresh (response?: Response) { + const headers: HeadersInit = { + "Accept": "application/json", + }; + response ??= await fetch(`${Env.API_ORIGIN}session`, { headers, credentials: "include" }); + const stateToken = response.headers.get("State-Token"); + if (stateToken) + localStorage.setItem("State-Token", stateToken); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const session = await response.json().catch(() => ({})); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + localStorage.setItem("Session-Auth-Services", JSON.stringify(session?.data?.authServices ?? {})); + } + + export function getStateToken () { + return localStorage.getItem("State-Token"); + } + + export function getAuthServices () { + const authServicesString = localStorage.getItem("Session-Auth-Services"); + return authServicesString && JSON.parse(authServicesString) || {}; + } +} + +export default Session; diff --git a/static/image/service/github.png b/static/image/service/github.png new file mode 100644 index 0000000..50b8175 Binary files /dev/null and b/static/image/service/github.png differ diff --git a/static/index.html b/static/index.html index ad1bef6..f6094e1 100644 --- a/static/index.html +++ b/static/index.html @@ -1,15 +1,16 @@ + fluff4.me / Queer Webnovels - - - - - - + + + + + + @@ -22,6 +23,7 @@ + - - - + + \ No newline at end of file