From 26638b00d57d263abf302fa7225e103db3a9d4a4 Mon Sep 17 00:00:00 2001 From: Daryl Ronningen Date: Tue, 6 Jul 2021 15:53:54 -0500 Subject: [PATCH] feat: base api handler --- .eslintrc | 22 ++++++++++++- src/api/apiClient.ts | 73 ++++++++++++++++++++++++++++++++++------- src/api/apiHandler.ts | 28 +++++++++------- src/api/apiHelper.ts | 24 ++++++++++++++ src/client/client.ts | 6 ++-- src/utils/defaults.ts | 2 +- src/utils/types.ts | 75 +++++++++++++++++++++---------------------- tsconfig.json | 3 +- 8 files changed, 164 insertions(+), 69 deletions(-) create mode 100644 src/api/apiHelper.ts diff --git a/.eslintrc b/.eslintrc index dbb5e84..9c999ad 100644 --- a/.eslintrc +++ b/.eslintrc @@ -116,7 +116,27 @@ { "case": "camelCase" } - ] + ], + // Import Rules + "import/order": [ + "error", + { + "newlines-between": "always-and-inside-groups", + "groups": [ + [ + "builtin", + "external", + "internal", + "sibling", + "parent", + "index", + "object", + "type" + ] + ] + } + ], + "import/newline-after-import": "error" }, "reportUnusedDisableDirectives": true } diff --git a/src/api/apiClient.ts b/src/api/apiClient.ts index 7a20e42..5bfbfa3 100644 --- a/src/api/apiClient.ts +++ b/src/api/apiClient.ts @@ -1,27 +1,78 @@ import { ApiHandler } from './apiHandler'; -import type { DeepRequired, IApiClientOptions } from '../utils/types'; +import type { Client } from '../client/client'; +import type { IRequestOptions } from '../utils/types'; export class ApiClient { - public handler: ApiHandler - public options: DeepRequired; + public client: Client; + public handler: ApiHandler; private _token: string; - public constructor(token: string, options: DeepRequired) { - this.options = options; + public constructor(client: Client, token: string) { + this.client = client; this._token = token; - this.handler = new ApiHandler(this._token, this.options); + this.handler = new ApiHandler(this.client, this._token); } - public async get(): Promise { + public async delete(options: IRequestOptions): Promise { + return this.handler.makeRequest({ + method: 'DELETE', + body: options.body, + headers: options.headers, + path: options.path, + requireAuth: options.requireAuth, + files: options.files, + reason: options.reason, + }); + } + + public async get(options: IRequestOptions): Promise { return this.handler.makeRequest({ method: 'GET', - body: '', - headers: {}, - path: '/', - requireAuth: false, + body: options.body, + headers: options.headers, + path: options.path, + requireAuth: options.requireAuth, + files: options.files, + reason: options.reason, + }); + } + + public async patch(options: IRequestOptions): Promise { + return this.handler.makeRequest({ + method: 'PATCH', + body: options.body, + headers: options.headers, + path: options.path, + requireAuth: options.requireAuth, + files: options.files, + reason: options.reason, + }); + } + + public async post(options: IRequestOptions): Promise { + return this.handler.makeRequest({ + method: 'POST', + body: options.body, + headers: options.headers, + path: options.path, + requireAuth: options.requireAuth, + files: options.files, + reason: options.reason, + }); + } + + public async put(options: IRequestOptions): Promise { + return this.handler.makeRequest({ + method: 'PUT', + body: options.body, + headers: options.headers, + path: options.path, + requireAuth: options.requireAuth, + files: options.files, + reason: options.reason, }); } } diff --git a/src/api/apiHandler.ts b/src/api/apiHandler.ts index 9b08b10..4d9bb77 100644 --- a/src/api/apiHandler.ts +++ b/src/api/apiHandler.ts @@ -1,18 +1,19 @@ import { AbortController } from 'abort-controller'; import { queue, QueueObject } from 'async'; import FormData from 'form-data'; -import fetch, { Headers } from 'node-fetch'; +import fetch from 'node-fetch'; -import type { DeepRequired, IApiClientOptions, IMakeRequestOptions } from '../utils/types'; +import type { Client } from '../client/client'; +import type { IMakeRequestOptions } from '../utils/types'; export class ApiHandler { - public options: DeepRequired; + public client: Client; public queue: QueueObject; private _token: string; - public constructor(token: string, options: DeepRequired) { - this.options = options; + public constructor(client: Client, token: string) { + this.client = client; this._token = token; this.queue = queue(async (requestOptions: IMakeRequestOptions, callback) => { @@ -21,10 +22,10 @@ export class ApiHandler { } public get baseApiUrl(): string { - return `${this.options.apiUrl}/v${this.options.apiVersion}`; + return `${this.client.options.api.apiUrl}/v${this.client.options.api.apiVersion}`; } - public async makeRequest(options: IMakeRequestOptions): Promise { + public async makeRequest(options: IMakeRequestOptions): Promise { const headers: Map = new Map(); if (options.headers) for (const prop in options.headers) headers.set(prop, options.headers[prop]!); @@ -43,18 +44,21 @@ export class ApiHandler { } const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), this.options.apiRequestTimeout); + const timeout = setTimeout(() => controller.abort(), this.client.options.api.apiRequestTimeout); try { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - const res = await fetch(`${this.baseApiUrl}${options.path}`, { method: options.method, headers: new Headers(headers), signal: controller.signal, body }); + const res = await fetch(`${this.baseApiUrl}${options.path}`, { method: options.method, headers, signal: controller.signal, body }); // TODO: handle Ratelimits - // TODO: Handle non-2xx responses - - return res.json() as unknown as T; + // TODO: Handle non-2xx responses (Will slowly add them as i get them) + if (res.ok) { + return res.json() as unknown as T; + } else { + return null; + } } finally { clearTimeout(timeout); } diff --git a/src/api/apiHelper.ts b/src/api/apiHelper.ts new file mode 100644 index 0000000..358d94b --- /dev/null +++ b/src/api/apiHelper.ts @@ -0,0 +1,24 @@ +import { ApiClient } from './apiClient'; + +import type { Client } from '../client/client'; + +export class ApiHelper { + public apiClient: ApiClient; + public client: Client; + + private _token: string; + + public constructor(client: Client, token: string) { + this.client = client; + this._token = token; + + this.apiClient = new ApiClient(this.client, this._token); + } + + public createMessage(channelId: string): void { + this.apiClient.get({ + path: `/channels/${channelId}/messages`, + requireAuth: true, + }); + } +} diff --git a/src/client/client.ts b/src/client/client.ts index 73ac269..e04597a 100644 --- a/src/client/client.ts +++ b/src/client/client.ts @@ -1,12 +1,12 @@ import _ from 'lodash'; -import { ApiClient } from '../api/apiClient'; +import { ApiHelper } from '../api/apiHelper'; import { defaults } from '../utils/defaults'; import type { DeepRequired, IClientOptions } from '../utils/types'; export class Client { - public api: ApiClient; + public api: ApiHelper; public options: DeepRequired; private _token: string; @@ -15,6 +15,6 @@ export class Client { this.options = _.merge(defaults.clientOptions, options as DeepRequired); this._token = token; - this.api = new ApiClient(this._token, this.options.api); + this.api = new ApiHelper(this, this._token); } } diff --git a/src/utils/defaults.ts b/src/utils/defaults.ts index b76a11e..a2a699f 100644 --- a/src/utils/defaults.ts +++ b/src/utils/defaults.ts @@ -3,9 +3,9 @@ import type { IDefaultOptions } from './types'; export const defaults: IDefaultOptions = { clientOptions: { api: { + apiRequestTimeout: 5000, apiUrl: 'https://discord.com/api', apiVersion: 9, - apiRequestTimeout: 5000, }, }, }; diff --git a/src/utils/types.ts b/src/utils/types.ts index 4d96ea1..b32d8e7 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -1,9 +1,8 @@ // Interfaces export interface IApiClientOptions { - apiUrl?: string; - - apiVersion?: number; - apiRequestTimeout?: number; + apiUrl?: string; + apiRequestTimeout?: number; + apiVersion?: number; } export interface IClientOptions { @@ -14,49 +13,47 @@ export interface IDefaultOptions { clientOptions: IClientOptions; } -export interface IMakeRequestOptions { - path: string; - reason?: string; +export interface IMakeRequestOptions extends IRequestOptions { + method: ApiMethods; +} - requireAuth?: boolean; - - body?: unknown; - - headers?: Record; - - method: ApiMethods; - - files?: { - name: string; - file: Buffer; - }[]; +export interface IRequestOptions { + path: string; + reason?: string; + requireAuth?: boolean; + body?: unknown; + headers?: Record; + files?: { + file: Buffer; + name: string; + }[]; } // Enums // Type Aliases -export type ApiMethods = 'GET' | 'POST' | 'DELETE'; +export type ApiMethods = 'DELETE' | 'GET' | 'PATCH' | 'POST' | 'PUT' ; export type Primitives = string | number | boolean | bigint | symbol | undefined | null; // eslint-disable-next-line @typescript-eslint/ban-types export type Builtin = Primitives | Function | Date | Error | RegExp; export type DeepRequired = T extends Builtin - ? NonNullable - : T extends Map - ? Map, DeepRequired> - : T extends ReadonlyMap - ? ReadonlyMap, DeepRequired> - : T extends WeakMap - ? WeakMap, DeepRequired> - : T extends Set - ? Set> - : T extends ReadonlySet - ? ReadonlySet> - : T extends WeakSet - ? WeakSet> - : T extends Promise - ? Promise> - // eslint-disable-next-line @typescript-eslint/ban-types - : T extends {} - ? { [K in keyof T]-?: DeepRequired } - : NonNullable; + ? NonNullable + : T extends Map + ? Map, DeepRequired> + : T extends ReadonlyMap + ? ReadonlyMap, DeepRequired> + : T extends WeakMap + ? WeakMap, DeepRequired> + : T extends Set + ? Set> + : T extends ReadonlySet + ? ReadonlySet> + : T extends WeakSet + ? WeakSet> + : T extends Promise + ? Promise> + // eslint-disable-next-line @typescript-eslint/ban-types + : T extends {} + ? { [K in keyof T]-?: DeepRequired } + : NonNullable; diff --git a/tsconfig.json b/tsconfig.json index 567727e..8730df8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -40,8 +40,7 @@ "noEmitOnError": false, "preserveConstEnums": true, "traceResolution": false, - "moduleResolution": "Node", - "baseUrl": "." + "moduleResolution": "Node" }, "include": [ "src/**/*.ts"