diff --git a/package-lock.json b/package-lock.json index 90dea64..7283770 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "ioredis": "^5.3.2", "morgan": "^1.10.0", "nest-winston": "^1.9.4", + "qs": "^6.11.2", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", "uuid": "^9.0.1", @@ -3269,6 +3270,20 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/bplist-parser": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", @@ -4827,6 +4842,20 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "node_modules/express/node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/express/node_modules/raw-body": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", @@ -7978,9 +8007,9 @@ ] }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", "dependencies": { "side-channel": "^1.0.4" }, diff --git a/package.json b/package.json index f6f939f..75ae916 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "ioredis": "^5.3.2", "morgan": "^1.10.0", "nest-winston": "^1.9.4", + "qs": "^6.11.2", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", "uuid": "^9.0.1", diff --git a/src/app.module.ts b/src/app.module.ts index fd37397..d5e6f47 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -18,12 +18,14 @@ import { SuccessResponseInterceptor } from '@/common/interceptor/success-respons import { ErrorExceptionFilter } from '@/common/filter/error-exception.filter'; import { FailExceptionFilter } from '@/common/filter/fail-exception.filter'; import { RedisCacheModule } from '@/client/redis-cache/redis-cache.module'; +import { HttpModule } from '@/client/http/http.module'; @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, }), + HttpModule, LoggerModule, RedisJwtModule, RedisCacheModule, diff --git a/src/client/http/fetch/fetch.client.ts b/src/client/http/fetch/fetch.client.ts new file mode 100644 index 0000000..6efe720 --- /dev/null +++ b/src/client/http/fetch/fetch.client.ts @@ -0,0 +1,57 @@ +import type { HttpClient } from '@/client/http/http-client.interface'; +import * as qs from 'qs'; + +export class FetchClient implements HttpClient { + private readonly _url: string; + private readonly _method: string; + private readonly _defaultHeaders = {}; + private _headers?: Record; + private _queryParams?: Record; + private _body: Record = {}; + + constructor(url: string, method: string) { + this._url = url; + this._method = method; + } + + headers(headers: Record): this { + this._headers = { ...headers }; + return this; + } + queryParams(params: Record): this { + this._queryParams = { ...params }; + return this; + } + + body(body: Record): this { + this._body = { ...body }; + return this; + } + + async send(): Promise { + return await fetch(this._getUri(), this._getOptions()).then((res) => + res.json(), + ); + } + + private _getUri() { + return this._queryParams + ? `${this._url}?${qs.stringify(this._queryParams, { + arrayFormat: 'repeat', + })}` + : this._url; + } + + private _getOptions() { + const options: Record = { + method: this._method, + headers: this._headers && { ...this._defaultHeaders, ...this._headers }, + }; + + if (this._method !== 'GET') { + options.body = JSON.stringify(this._body); + } + + return options; + } +} diff --git a/src/client/http/fetch/fetch.service.ts b/src/client/http/fetch/fetch.service.ts new file mode 100644 index 0000000..e512ca0 --- /dev/null +++ b/src/client/http/fetch/fetch.service.ts @@ -0,0 +1,25 @@ +import type { HttpClient } from '@/client/http/http-client.interface'; +import { HttpClientService } from '@/client/http/http-client.service'; +import { FetchClient } from '@/client/http/fetch/fetch.client'; + +export class FetchService extends HttpClientService { + override get(url: string): Omit { + return new FetchClient(url, 'GET'); + } + + override post(url: string): HttpClient { + return new FetchClient(url, 'POST'); + } + + override put(url: string): HttpClient { + return new FetchClient(url, 'PUT'); + } + + override delete(url: string): HttpClient { + return new FetchClient(url, 'DELETE'); + } + + override patch(url: string): HttpClient { + return new FetchClient(url, 'PATCH'); + } +} diff --git a/src/client/http/http-client.interface.ts b/src/client/http/http-client.interface.ts new file mode 100644 index 0000000..7c1eb59 --- /dev/null +++ b/src/client/http/http-client.interface.ts @@ -0,0 +1,6 @@ +export interface HttpClient { + queryParams(params: Record): this; + headers(headers: Record): this; + body(body: Record): this; + send(): Promise; +} diff --git a/src/client/http/http-client.service.ts b/src/client/http/http-client.service.ts new file mode 100644 index 0000000..80521bd --- /dev/null +++ b/src/client/http/http-client.service.ts @@ -0,0 +1,9 @@ +import { HttpClient } from '@/client/http/http-client.interface'; + +export abstract class HttpClientService { + abstract get(url: string): Omit; + abstract post(url: string): HttpClient; + abstract put(url: string): HttpClient; + abstract delete(url: string): HttpClient; + abstract patch(url: string): HttpClient; +} diff --git a/src/client/http/http.module.ts b/src/client/http/http.module.ts new file mode 100644 index 0000000..ef4f5df --- /dev/null +++ b/src/client/http/http.module.ts @@ -0,0 +1,10 @@ +import { Global, Module } from '@nestjs/common'; +import { HttpClientService } from '@/client/http/http-client.service'; +import { FetchService } from '@/client/http/fetch/fetch.service'; + +@Global() +@Module({ + providers: [{ provide: HttpClientService, useClass: FetchService }], + exports: [HttpClientService], +}) +export class HttpModule {}