From e74b5e1c130ce5ff881645e328e98a4151c4ad10 Mon Sep 17 00:00:00 2001 From: spiral Date: Tue, 1 Feb 2022 15:24:45 -0500 Subject: [PATCH] refactor: generalize API library --- src/api/errors.ts | 28 +++ src/api/group.ts | 40 ----- src/api/index.ts | 284 ++++-------------------------- src/api/member.ts | 60 ------- src/api/system.ts | 43 ----- src/api/types.ts | 75 ++++++++ src/lib/group/Body.svelte | 4 +- src/lib/group/Edit.svelte | 12 +- src/lib/group/List.svelte | 9 +- src/lib/group/MemberEdit.svelte | 12 +- src/lib/group/Privacy.svelte | 10 +- src/lib/member/Body.svelte | 4 +- src/lib/member/Edit.svelte | 12 +- src/lib/member/GroupEdit.svelte | 12 +- src/lib/member/List.svelte | 10 +- src/lib/member/Privacy.svelte | 10 +- src/lib/member/ProxyTags.svelte | 10 +- src/lib/system/Body.svelte | 5 +- src/lib/system/Edit.svelte | 12 +- src/lib/system/Main.svelte | 5 +- src/lib/system/Privacy.svelte | 5 +- src/lib/system/PrivacyEdit.svelte | 12 +- src/pages/Dash.svelte | 14 +- src/pages/Home.svelte | 7 +- src/pages/profiles/Main.svelte | 15 +- src/pages/status.svelte | 6 +- 26 files changed, 229 insertions(+), 487 deletions(-) create mode 100644 src/api/errors.ts delete mode 100644 src/api/group.ts delete mode 100644 src/api/member.ts delete mode 100644 src/api/system.ts create mode 100644 src/api/types.ts diff --git a/src/api/errors.ts b/src/api/errors.ts new file mode 100644 index 00000000..7c5aad7c --- /dev/null +++ b/src/api/errors.ts @@ -0,0 +1,28 @@ +enum ErrorType { + Unknown = 0, + InvalidToken = 401, + NotFound = 404, + InternalServerError = 500, +} + +interface ApiError { + code: number, + type: ErrorType, + message?: string, + data?: any, +} + +export function parse(code: number, data?: string): ApiError { + var type = ErrorType[ErrorType[code]] ?? ErrorType.Unknown; + if (code >= 500) type = ErrorType.InternalServerError; + + var err: ApiError = { code, type }; + + if (data) { + var d = JSON.parse(data); + err.message = d.message; + err.data = d; + } + + return err; +} diff --git a/src/api/group.ts b/src/api/group.ts deleted file mode 100644 index 6369ee93..00000000 --- a/src/api/group.ts +++ /dev/null @@ -1,40 +0,0 @@ -interface GroupPrivacy { - description_privacy?: string, - icon_privacy?: string, - list_privacy?: string, - visibility?: string -} - -export default class Group { - id?: string; - uuid?: string; - name?: string; - display_name?: string; - description?: string; - icon?: string; - banner?: string; - color?: string; - privacy?: GroupPrivacy; - created?: string; - members?: string[]; - - constructor(data: Group) { - this.id = data.id; - this.uuid = data.uuid; - this.name = data.name; - this.display_name = data.display_name; - this.description = data.description; - this.icon = data.icon; - this.banner = data.banner; - this.color = data.color; - this.created = data.created; - this.members = data.members; - if (data.privacy) { - this.privacy = {} - this.privacy.description_privacy = data.privacy.description_privacy; - this.privacy.icon_privacy = data.privacy.icon_privacy; - this.privacy.list_privacy = data.privacy.list_privacy; - this.privacy.visibility = data.privacy.visibility; - } - } -} \ No newline at end of file diff --git a/src/api/index.ts b/src/api/index.ts index 4c0946a9..7f95846e 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,253 +1,43 @@ -import axios, { AxiosInstance, Method, AxiosResponse, AxiosRequestConfig, Axios } from 'axios'; -import Sys from './system'; -import Member from './member'; -import Group from './group'; +import axios from 'axios'; +const baseUrl = () => localStorage.isBeta ? "https://api.beta.pluralkit.me" : "https://api.pluralkit.me"; -type FieldError = { - message: string +const methods = ['get', 'post', 'delete', 'patch', 'put']; +const noop = () => {}; + +export default function() { + const route = []; + const handler = { + get(_, name) { + if (route.length == 0 && name != "private") + route.push("v2"); + if (methods.includes(name)) { + return ({ data = undefined, auth = true, token = null, query = null } = {}) => new Promise((res, rej) => axios({ + url: baseUrl() + "/" + route.join("/") + (query ? `?${Object.keys(query).map(x => `${x}=${query[x]}`).join("&")}` : ""), + method: name, + headers: { + authorization: token ?? (auth ? localStorage.getItem("pk-token") : undefined), + "content-type": name == "get" ? undefined : "application/json" + }, + data: !!data ? JSON.stringify(data) : undefined, + validateStatus: () => true, + }).then((resp) => res(parseData(resp.status, resp.data))).catch(rej)); + } + route.push(name); + return new Proxy(noop, handler); + }, + apply(target, _, args) { + route.push(...args.filter(x => x != null)); + return new Proxy(noop, handler); + } + } + return new Proxy(noop, handler); } -export default class PKAPI { - - ROUTES = { - GET_SYSTEM: (sid?: string) => sid ? `/systems/${sid}` : `/systems/@me`, - GET_MEMBER_LIST: (sid?: string) => sid ? `/systems/${sid}/members` : `/systems/@me/members`, - GET_MEMBER: (mid: string) => `/members/${mid}`, - GET_GROUP_LIST: (sid?: string, members?: boolean) => `${sid ? `/systems/${sid}/groups` : `/systems/@me/groups`}` + `${members ? `?with_members=true` : ""}`, - - PATCH_SYSTEM: () => `/systems/@me`, - PATCH_GROUP: (gid: string) => `/groups/${gid}`, - PATCH_MEMBER: (mid: string) => `/members/${mid}`, - - POST_MEMBER: () => `/members`, - POST_MEMBER_GROUP: (mid: string, removing: boolean) => !removing ? `/members/${mid}/groups/add` : `/members/${mid}/groups/remove`, - POST_GROUP_MEMBER: (gid: string, removing: boolean) => !removing ? `/groups/${gid}/members/add` : `/groups/${gid}/members/remove`, - - DELETE_MEMBER: (mid: string) => `/members/${mid}`, - DELETE_GROUP: (gid: string) => `/groups/${gid}` - } - - baseUrl: string; - instance: AxiosInstance - - constructor(baseUrl?: string) { - this.baseUrl = baseUrl || 'https://api.pluralkit.me'; - - this.instance = axios.create({ - baseURL: this.baseUrl + '/v2' - }) - } - - async getSystem(options: { token?: string, id?: any}) { - if (!options.token && !options.id) { - throw new Error("Must pass a token or id.") - } - var system: Sys; - var res: AxiosResponse; - try { - res = await this.handle(this.ROUTES.GET_SYSTEM(options.id ? options.id : ""), 'GET', {token: !options.id ? options.token : ""}); - if (res.status === 200) system = new Sys(res.data); - else this.handleErrors(res); - } catch (error) { - throw new Error(error.message); - } - return system; - } - - async patchSystem(options: {token: string, data: any}) { - var body = new Sys(options.data); - var system: Sys; - var res: AxiosResponse; - try { - res = await this.handle(this.ROUTES.PATCH_SYSTEM(), 'PATCH', {token: options.token, body: body}); - if (res.status === 200) system = new Sys(res.data); - else this.handleErrors(res); - } catch (error) { - throw new Error(error.message); - } - return system; - } - - async getMemberList(options: { token?: string, id?: any}) { - if (!options.token && !options.id) { - throw new Error("Must pass a token or id.") - } - var members: Member[] = []; - var res: AxiosResponse; - try { - res = await this.handle(this.ROUTES.GET_MEMBER_LIST(options.id ? options.id : ""), 'GET', {token: !options.id ? options.token : ""}); - if (res.status === 200) { - let resObject: any = res.data; - resObject.forEach(m => { - let member = new Member(m); - members.push(member); - }) - } - else this.handleErrors(res); - } catch (error) { - throw new Error(error.message); - } - return members; - } - - async getMember(options: {id: any}) { - if (!options.id) { - throw new Error("Must pass an id.") - } - var member: Member; - var res: AxiosResponse; - try { - res = await this.handle(this.ROUTES.GET_MEMBER(options.id), 'GET', {}); - if (res.status === 200) member = new Member(res.data); - else this.handleErrors(res); - } catch (error) { - throw new Error(error.message); - } - return member; - } - - async patchMember(options: {token: string, id: any, data: any}) { - var body = new Member(options.data); - var member: Member; - var res: AxiosResponse; - try { - res = await this.handle(this.ROUTES.PATCH_MEMBER(options.id), 'PATCH', {token: options.token, body: body}); - if (res.status === 200) member = new Member(res.data); - else this.handleErrors(res); - } catch (error) { - throw new Error(error.message); - } - return member; - } - - async postMember(options: {token: any, data: any}) { - if (!options.token) throw new Error("Must pass a token."); - var body = new Member(options.data); - var member: Member; - var res: AxiosResponse; - try { - res = await this.handle(this.ROUTES.POST_MEMBER(), 'POST', {token: options.token, body: body}); - if (res.status === 200) member = new Member(res.data); - else this.handleErrors(res); - } catch (error) { - throw new Error(error.message); - } - return member; - } - - async postMemberGroups(options: {token: string, id: string, data: any, removing?: boolean}) { - var res: AxiosResponse; - try { - res = await this.handle(this.ROUTES.POST_MEMBER_GROUP(options.id, options.removing), 'POST', {token: options.token, body: options.data}); - if (res.status !== 204) this.handleErrors(res); - } catch (error) { - throw new Error(error.message); - } - } - - async deleteMember(options: {token: string, id: string}) { - var res: AxiosResponse; - try { - res = await this.handle(this.ROUTES.DELETE_MEMBER(options.id), 'DELETE', {token: options.token}); - if (res.status !== 204 ) this.handleErrors(res); - } catch (error) { - throw new Error(error.message); - } - } - - async getGroupList(options: {token?: string, id?: any, members?: boolean}) { - if (!options.token && !options.id) { - throw new Error("Must pass a token or id."); - } - if (!options.members) options.members = false; - - var groups: Group[] = []; - var res: AxiosResponse; - try { - res = await this.handle(this.ROUTES.GET_GROUP_LIST(options.id ? options.id : "", options.members), 'GET', {token: !options.id ? options.token : ""}); - if (res.status === 200) { - let resObject: any = res.data; - resObject.forEach(g => { - let group = new Group(g); - groups.push(group); - }) - } - else this.handleErrors(res); - } catch (error) { - throw new Error(error.message); - } - return groups; - } - - async patchGroup(options: {token: string, id: any, data: any}) { - var body = new Group(options.data); - var group: Group; - var res: AxiosResponse; - try { - res = await this.handle(this.ROUTES.PATCH_GROUP(options.id), 'PATCH', {token: options.token, body: body}); - if (res.status === 200) group = new Group(res.data); - else this.handleErrors(res); - } catch (error) { - throw new Error(error.message); - } - return group; - } - - async postGroupMembers(options: {token: string, id: string, data: any, removing?: boolean}) { - var res: AxiosResponse; - try { - res = await this.handle(this.ROUTES.POST_GROUP_MEMBER(options.id, options.removing), 'POST', {token: options.token, body: options.data}); - if (res.status !== 204) this.handleErrors(res); - } catch (error) { - throw new Error(error.message); - } - } - - async deleteGroup(options: {token: string, id: string}) { - var res: AxiosResponse; - try { - res = await this.handle(this.ROUTES.DELETE_GROUP(options.id), 'DELETE', {token: options.token}); - if (res.status !== 204 ) this.handleErrors(res); - } catch (error) { - throw new Error(error.message); - } - } - - handleErrors(res: any) { - if (res.status === 500) throw new Error("500: Internal server error."); - else if (res.status === 401) throw new Error("401: Your token is invalid."); - else { - let errorObject: any = res.data - if (errorObject.code) { - if (errorObject.code === 40001) { - var message: string; - for (var key in errorObject.errors) { - var val = errorObject.errors[key]; - } - } else { - throw new Error(errorObject.message); - } - } - } - } - - async handle(url: string, method: Method, options: {token?: string, body?: object}) { - var headers = {} - var request: AxiosRequestConfig = {url, method, headers} - - if(options.token) request.headers["Authorization"] = options.token; - if (options.body) { - request.headers["Content-Type"] = "application/json"; - request.data = JSON.stringify(options.body); - } - - try { - var res = await this.instance(request); - } catch (error) { - res = error.response; - } - return res; - } +import * as errors from './errors'; +function parseData(code: number, data: any) { + if (code == 200) return data; + if (code == 204) return; + throw errors.parse(code, data); } \ No newline at end of file diff --git a/src/api/member.ts b/src/api/member.ts deleted file mode 100644 index 72bf0c54..00000000 --- a/src/api/member.ts +++ /dev/null @@ -1,60 +0,0 @@ -interface MemberPrivacy { - visibility?: string, - description_privacy?: string, - name_privacy?: string, - birthday_privacy?: string, - pronoun_privacy?: string, - avatar_privacy?: string, - metadata_privacy?: string -} - -type proxytag = { - prefix?: string, - suffix?: string -} - -export default class Member { - id?: string; - uuid?: string; - name?: string; - display_name?: string; - color?: string; - birthday?: string; - pronouns?: string; - avatar_url?: string; - banner?: string; - description?: string; - created?: string; - keep_proxy?: boolean - system?: string; - proxy_tags?: Array; - privacy?: MemberPrivacy - - constructor(data: Member) { - this.id = data.id; - this.uuid = data.uuid; - this.name = data.name; - this.display_name = data.display_name; - this.color = data.color; - this.birthday = data.birthday; - this.pronouns = data.pronouns; - this.avatar_url = data.avatar_url; - this.banner = data.banner; - this.description = data.description; - this.created = data.created; - this.system = data.system; - this.proxy_tags = data.proxy_tags; - this.keep_proxy = data.keep_proxy; - if (data.privacy) { - this.privacy = { - visibility: data.privacy.visibility, - description_privacy: data.privacy.description_privacy, - name_privacy: data.privacy.name_privacy, - birthday_privacy: data.privacy.birthday_privacy, - pronoun_privacy: data.privacy.pronoun_privacy, - avatar_privacy: data.privacy.avatar_privacy, - metadata_privacy: data.privacy.metadata_privacy - } - } - } -} \ No newline at end of file diff --git a/src/api/system.ts b/src/api/system.ts deleted file mode 100644 index 6a01cdb7..00000000 --- a/src/api/system.ts +++ /dev/null @@ -1,43 +0,0 @@ -interface SystemPrivacy { - description_privacy?: string, - member_list_privacy?: string, - front_privacy?: string, - front_history_privacy?: string, - group_list_privacy?: string -} - -export default class Sys { - id?: string; - uuid?: string; - name?: string; - description?: string; - tag?: string; - avatar_url?: string; - banner?: string; - timezone?: string; - created?: string; - privacy?: SystemPrivacy; - color?: string; - - constructor(data: Sys) { - this.id = data.id; - this.uuid = data.uuid; - this.name = data.name; - this.description = data.description; - this.tag = data.tag; - this.avatar_url = data.avatar_url; - this.banner = data.banner; - this.timezone = data.timezone; - this.created = data.created; - this.color = data.color; - if (data.privacy) { - this.privacy = { - description_privacy: data.privacy.description_privacy, - member_list_privacy: data.privacy.member_list_privacy, - front_privacy: data.privacy.front_privacy, - front_history_privacy: data.privacy.front_history_privacy, - group_list_privacy: data.privacy.group_list_privacy - } - } - } -} \ No newline at end of file diff --git a/src/api/types.ts b/src/api/types.ts new file mode 100644 index 00000000..e16815aa --- /dev/null +++ b/src/api/types.ts @@ -0,0 +1,75 @@ +interface SystemPrivacy { + description_privacy?: string, + member_list_privacy?: string, + front_privacy?: string, + front_history_privacy?: string, + group_list_privacy?: string +} + +export interface System { + id?: string; + uuid?: string; + name?: string; + description?: string; + tag?: string; + avatar_url?: string; + banner?: string; + timezone?: string; + created?: string; + privacy?: SystemPrivacy; + color?: string; +} + +interface MemberPrivacy { + visibility?: string, + description_privacy?: string, + name_privacy?: string, + birthday_privacy?: string, + pronoun_privacy?: string, + avatar_privacy?: string, + metadata_privacy?: string +} + +interface proxytag { + prefix?: string, + suffix?: string +} + +export interface Member { + id?: string; + uuid?: string; + name?: string; + display_name?: string; + color?: string; + birthday?: string; + pronouns?: string; + avatar_url?: string; + banner?: string; + description?: string; + created?: string; + keep_proxy?: boolean + system?: string; + proxy_tags?: Array; + privacy?: MemberPrivacy +} + +export interface GroupPrivacy { + description_privacy?: string, + icon_privacy?: string, + list_privacy?: string, + visibility?: string +} + +export interface Group { + id?: string; + uuid?: string; + name?: string; + display_name?: string; + description?: string; + icon?: string; + banner?: string; + color?: string; + privacy?: GroupPrivacy; + created?: string; + members?: string[]; +} \ No newline at end of file diff --git a/src/lib/group/Body.svelte b/src/lib/group/Body.svelte index 874a75b0..211f6b08 100644 --- a/src/lib/group/Body.svelte +++ b/src/lib/group/Body.svelte @@ -2,12 +2,12 @@ import { Row, Col, Modal, Image, Button, CardBody, ModalHeader, ModalBody, ModalFooter, Spinner } from 'sveltestrap'; import moment from 'moment'; import { toHTML } from 'discord-markdown'; - import type Group from '../../api/group'; import Edit from './Edit.svelte'; import twemoji from 'twemoji'; import Privacy from './Privacy.svelte'; - import type Member from 'src/api/member'; import MemberEdit from './MemberEdit.svelte'; + + import { Member, Group } from '../../api/types'; export let group: Group; let editMode: boolean = false; diff --git a/src/lib/group/Edit.svelte b/src/lib/group/Edit.svelte index 9122436e..b67794bb 100644 --- a/src/lib/group/Edit.svelte +++ b/src/lib/group/Edit.svelte @@ -1,8 +1,8 @@