Fix image-upload, http-client and config
This commit is contained in:
parent
50d36f268f
commit
69dd12375c
@ -13,14 +13,18 @@ rules:
|
|||||||
ignorePattern: '^import .* from '
|
ignorePattern: '^import .* from '
|
||||||
comma-dangle:
|
comma-dangle:
|
||||||
- warn
|
- warn
|
||||||
- always-multiline
|
- arrays: always-multiline
|
||||||
|
objects: always-multiline
|
||||||
|
imports: always-multiline
|
||||||
|
exports: always-multiline
|
||||||
|
functions: always-multiline
|
||||||
|
|
||||||
# semi:
|
# semi:
|
||||||
# - warn
|
# - warn
|
||||||
# - never
|
# - never
|
||||||
# interface-name-prefix:
|
# interface-name-prefix:
|
||||||
'@typescript-eslint/member-delimiter-style':
|
'@typescript-eslint/member-delimiter-style':
|
||||||
- error
|
- warn
|
||||||
- multiline:
|
- multiline:
|
||||||
delimiter: none
|
delimiter: none
|
||||||
singleline:
|
singleline:
|
||||||
|
|||||||
4
packages/config/.eslintrc.yaml
Normal file
4
packages/config/.eslintrc.yaml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
extends:
|
||||||
|
- ../../.eslintrc.yaml
|
||||||
|
rules:
|
||||||
|
'@typescript-eslint/no-explicit-any': off
|
||||||
@ -4,7 +4,7 @@ export class Config {
|
|||||||
get(key: string) {
|
get(key: string) {
|
||||||
let value = this.config
|
let value = this.config
|
||||||
key.split('.').forEach(k => {
|
key.split('.').forEach(k => {
|
||||||
if (!value.hasOwnProperty(k)) {
|
if (!Object.prototype.hasOwnProperty.call(value, k)) {
|
||||||
throw new Error(`Property "${k}" from "${key}" does not exist`)
|
throw new Error(`Property "${k}" from "${key}" does not exist`)
|
||||||
}
|
}
|
||||||
value = value[k]
|
value = value[k]
|
||||||
@ -15,7 +15,7 @@ export class Config {
|
|||||||
has(key: string) {
|
has(key: string) {
|
||||||
let c = this.config
|
let c = this.config
|
||||||
return key.split('.').every(k => {
|
return key.split('.').every(k => {
|
||||||
const has = c.hasOwnProperty(k)
|
const has = Object.prototype.hasOwnProperty.call(c, k)
|
||||||
if (has) {
|
if (has) {
|
||||||
c = c[k]
|
c = c[k]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import {ConfigReader} from './ConfigReader'
|
import { writeFileSync } from 'fs'
|
||||||
import {join} from 'path'
|
import { join } from 'path'
|
||||||
import {writeFileSync} from 'fs'
|
import { ConfigReader } from './ConfigReader'
|
||||||
|
|
||||||
describe('ConfigReader', () => {
|
describe('ConfigReader', () => {
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
|
import loggerFactory, { Logger } from '@rondo.dev/logger'
|
||||||
import { readFileSync } from 'fs'
|
import { readFileSync } from 'fs'
|
||||||
import YAML from 'js-yaml'
|
import YAML from 'js-yaml'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { Config } from './Config'
|
import { Config } from './Config'
|
||||||
import { findPackageRoot } from './findPackageRoot'
|
import { findPackageRoot } from './findPackageRoot'
|
||||||
import loggerFactory, {ILogger} from '@rondo.dev/logger'
|
|
||||||
|
|
||||||
const isObject = (value: any) => value !== null && typeof value === 'object'
|
const isObject = (value: unknown) => value !== null && typeof value === 'object'
|
||||||
|
|
||||||
export class ConfigReader {
|
export class ConfigReader {
|
||||||
protected readonly config: any = {}
|
protected readonly config: any = {}
|
||||||
@ -16,7 +16,7 @@ export class ConfigReader {
|
|||||||
readonly path: string,
|
readonly path: string,
|
||||||
readonly cwd: string | undefined = process.cwd(),
|
readonly cwd: string | undefined = process.cwd(),
|
||||||
readonly environment = 'CONFIG',
|
readonly environment = 'CONFIG',
|
||||||
readonly logger: ILogger = loggerFactory.getLogger('config'),
|
readonly logger: Logger = loggerFactory.getLogger('config'),
|
||||||
) {
|
) {
|
||||||
const packageRoot = path && findPackageRoot(path)
|
const packageRoot = path && findPackageRoot(path)
|
||||||
this.locations = packageRoot ? [packageRoot] : []
|
this.locations = packageRoot ? [packageRoot] : []
|
||||||
@ -93,7 +93,8 @@ export class ConfigReader {
|
|||||||
// }
|
// }
|
||||||
const value = src[key]
|
const value = src[key]
|
||||||
if (isObject(value) && !Array.isArray(value)) {
|
if (isObject(value) && !Array.isArray(value)) {
|
||||||
if (!dest.hasOwnProperty(key) ||
|
if (
|
||||||
|
!Object.prototype.hasOwnProperty.call(dest, key) ||
|
||||||
Array.isArray(dest[key]) ||
|
Array.isArray(dest[key]) ||
|
||||||
!isObject(dest[key])
|
!isObject(dest[key])
|
||||||
) {
|
) {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import {resolve, join} from 'path'
|
import { Stats, statSync } from 'fs'
|
||||||
import {statSync, Stats} from 'fs'
|
import { join, resolve } from 'path'
|
||||||
|
|
||||||
function findNearestDirectory(
|
function findNearestDirectory(
|
||||||
dir: string, filename: string,
|
dir: string, filename: string,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
export * from './Config'
|
export * from './Config'
|
||||||
|
|
||||||
import {ConfigReader} from './ConfigReader'
|
import { ConfigReader } from './ConfigReader'
|
||||||
export default ConfigReader
|
export default ConfigReader
|
||||||
|
|||||||
4
packages/http-client/.eslintrc.yaml
Normal file
4
packages/http-client/.eslintrc.yaml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
extends:
|
||||||
|
- ../../.eslintrc.yaml
|
||||||
|
rules:
|
||||||
|
'@typescript-eslint/no-explicit-any': off
|
||||||
@ -1,140 +1,51 @@
|
|||||||
import axios from 'axios'
|
import {Method, Routes} from '@rondo.dev/http-types'
|
||||||
import {IHTTPClient} from './IHTTPClient'
|
import {TypedRequestParams} from './TypedRequestParams'
|
||||||
import {IHeader} from './IHeader'
|
|
||||||
import {TMethod, IRoutes} from '@rondo.dev/http-types'
|
|
||||||
import {URLFormatter} from './URLFormatter'
|
|
||||||
import {IRequest} from './IRequest'
|
|
||||||
import {IResponse} from './IResponse'
|
|
||||||
import {ITypedRequestParams} from './ITypedRequestParams'
|
|
||||||
|
|
||||||
interface IRequestor {
|
export interface HTTPClient<T extends Routes> {
|
||||||
request: (params: IRequest) => Promise<IResponse>
|
request<
|
||||||
}
|
|
||||||
|
|
||||||
export class HTTPClient<T extends IRoutes> implements IHTTPClient<T> {
|
|
||||||
protected readonly requestor: IRequestor
|
|
||||||
protected readonly formatter: URLFormatter
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
protected readonly baseURL = '',
|
|
||||||
protected readonly headers?: IHeader,
|
|
||||||
) {
|
|
||||||
this.requestor = this.createRequestor()
|
|
||||||
this.formatter = new URLFormatter()
|
|
||||||
}
|
|
||||||
|
|
||||||
protected createRequestor(): IRequestor {
|
|
||||||
return axios.create({
|
|
||||||
baseURL: this.baseURL,
|
|
||||||
headers: this.headers,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async request<
|
|
||||||
P extends keyof T & string,
|
P extends keyof T & string,
|
||||||
M extends TMethod,
|
M extends Method,
|
||||||
>(params: ITypedRequestParams<T, P, M>): Promise<T[P][M]['response']> {
|
>(params: TypedRequestParams<T, P, M>): Promise<T[P][M]['response']>
|
||||||
|
|
||||||
const url = this.formatter.format(params.path, params.params)
|
|
||||||
|
|
||||||
const response = await this.requestor.request({
|
|
||||||
method: params.method,
|
|
||||||
url,
|
|
||||||
params: params.query,
|
|
||||||
data: params.body,
|
|
||||||
})
|
|
||||||
|
|
||||||
return response.data
|
|
||||||
}
|
|
||||||
|
|
||||||
get<P extends keyof T & string>(
|
get<P extends keyof T & string>(
|
||||||
path: P,
|
path: P,
|
||||||
query?: T[P]['get']['query'],
|
query?: T[P]['get']['query'],
|
||||||
params?: T[P]['get']['params'],
|
params?: T[P]['get']['params'],
|
||||||
) {
|
): Promise<T[P]['get']['response']>
|
||||||
return this.request({
|
|
||||||
method: 'get',
|
|
||||||
path,
|
|
||||||
query,
|
|
||||||
params,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
post<P extends keyof T & string>(
|
post<P extends keyof T & string>(
|
||||||
path: P,
|
path: P,
|
||||||
body: T[P]['post']['body'],
|
body: T[P]['post']['body'],
|
||||||
params?: T[P]['post']['params'],
|
params?: T[P]['post']['params'],
|
||||||
) {
|
): Promise<T[P]['post']['response']>
|
||||||
return this.request({
|
|
||||||
method: 'post',
|
|
||||||
path,
|
|
||||||
body,
|
|
||||||
params,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
put<P extends keyof T & string>(
|
put<P extends keyof T & string>(
|
||||||
path: P,
|
path: P,
|
||||||
body: T[P]['put']['body'],
|
body: T[P]['put']['body'],
|
||||||
params?: T[P]['put']['params'],
|
params?: T[P]['put']['params'],
|
||||||
) {
|
): Promise<T[P]['put']['response']>
|
||||||
return this.request({
|
|
||||||
method: 'put',
|
|
||||||
path,
|
|
||||||
body,
|
|
||||||
params,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
delete<P extends keyof T & string>(
|
delete<P extends keyof T & string>(
|
||||||
path: P,
|
path: P,
|
||||||
body: T[P]['delete']['body'],
|
body: T[P]['delete']['body'],
|
||||||
params?: T[P]['delete']['params'],
|
params?: T[P]['delete']['params'],
|
||||||
) {
|
): Promise<T[P]['delete']['response']>
|
||||||
return this.request({
|
|
||||||
method: 'delete',
|
|
||||||
path,
|
|
||||||
body,
|
|
||||||
params,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
head<P extends keyof T & string>(
|
head<P extends keyof T & string>(
|
||||||
path: P,
|
path: P,
|
||||||
query?: T[P]['head']['query'],
|
query?: T[P]['head']['query'],
|
||||||
params?: T[P]['head']['params'],
|
params?: T[P]['head']['params'],
|
||||||
) {
|
): Promise<T[P]['head']['response']>
|
||||||
return this.request({
|
|
||||||
method: 'head',
|
|
||||||
path,
|
|
||||||
params,
|
|
||||||
query,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
options<P extends keyof T & string>(
|
options<P extends keyof T & string>(
|
||||||
path: P,
|
path: P,
|
||||||
query?: T[P]['options']['query'],
|
query?: T[P]['options']['query'],
|
||||||
params?: T[P]['options']['params'],
|
params?: T[P]['options']['params'],
|
||||||
) {
|
): Promise<T[P]['options']['response']>
|
||||||
return this.request({
|
|
||||||
method: 'options',
|
|
||||||
path,
|
|
||||||
params,
|
|
||||||
query,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
patch<P extends keyof T & string>(
|
patch<P extends keyof T & string>(
|
||||||
path: P,
|
path: P,
|
||||||
body: T[P]['patch']['body'],
|
body: T[P]['patch']['body'],
|
||||||
params?: T[P]['patch']['params'],
|
params?: T[P]['patch']['params'],
|
||||||
) {
|
): Promise<T[P]['patch']['response']>
|
||||||
return this.request({
|
|
||||||
method: 'patch',
|
|
||||||
path,
|
|
||||||
body,
|
|
||||||
params,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,32 +1,32 @@
|
|||||||
import {HTTPClient} from './HTTPClient'
|
import {SimpleHTTPClient} from './SimpleHTTPClient'
|
||||||
import {IRequest} from './IRequest'
|
import {Request} from './Request'
|
||||||
import {IResponse} from './IResponse'
|
import {Response} from './Response'
|
||||||
import {IRoutes, TMethod} from '@rondo.dev/http-types'
|
import {Routes, Method} from '@rondo.dev/http-types'
|
||||||
import {ITypedRequestParams} from './ITypedRequestParams'
|
import {TypedRequestParams} from './TypedRequestParams'
|
||||||
|
|
||||||
interface IReqRes {
|
interface ReqRes {
|
||||||
req: IRequest
|
req: Request
|
||||||
res: IResponse
|
res: Response
|
||||||
}
|
}
|
||||||
|
|
||||||
export class HTTPClientError extends Error {
|
export class HTTPClientError extends Error {
|
||||||
constructor(readonly request: IRequest, readonly response: IResponse) {
|
constructor(readonly request: Request, readonly response: Response) {
|
||||||
super('HTTP Status: ' + response.status)
|
super('HTTP Status: ' + response.status)
|
||||||
Error.captureStackTrace(this)
|
Error.captureStackTrace(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IRequestStatus {
|
export interface RequestStatus {
|
||||||
request: IRequest
|
request: Request
|
||||||
finished: boolean
|
finished: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export class HTTPClientMock<T extends IRoutes> extends HTTPClient<T> {
|
export class HTTPClientMock<T extends Routes> extends SimpleHTTPClient<T> {
|
||||||
mocks: {[key: string]: IResponse} = {}
|
mocks: {[key: string]: Response} = {}
|
||||||
requests: IRequestStatus[] = []
|
requests: RequestStatus[] = []
|
||||||
|
|
||||||
protected waitPromise?: {
|
protected waitPromise?: {
|
||||||
resolve: (r: IReqRes) => void
|
resolve: (r: ReqRes) => void
|
||||||
reject: (err: Error) => void
|
reject: (err: Error) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,15 +39,15 @@ export class HTTPClientMock<T extends IRoutes> extends HTTPClient<T> {
|
|||||||
*/
|
*/
|
||||||
createRequestor() {
|
createRequestor() {
|
||||||
return {
|
return {
|
||||||
request: (req: IRequest): Promise<IResponse> => {
|
request: (req: Request): Promise<Response> => {
|
||||||
const currentRequest: IRequestStatus = {
|
const currentRequest: RequestStatus = {
|
||||||
request: req,
|
request: req,
|
||||||
finished: false,
|
finished: false,
|
||||||
}
|
}
|
||||||
this.requests.push(currentRequest)
|
this.requests.push(currentRequest)
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const key = this.serialize(req)
|
const key = this.serialize(req)
|
||||||
if (!this.mocks.hasOwnProperty(key)) {
|
if (!Object.prototype.hasOwnProperty.call(this.mocks, key)) {
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
const err = new Error(
|
const err = new Error(
|
||||||
'No mock for request: ' + key + '\nAvailable mocks: ' +
|
'No mock for request: ' + key + '\nAvailable mocks: ' +
|
||||||
@ -76,7 +76,7 @@ export class HTTPClientMock<T extends IRoutes> extends HTTPClient<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected serialize(req: IRequest) {
|
protected serialize(req: Request) {
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
method: req.method,
|
method: req.method,
|
||||||
url: req.url,
|
url: req.url,
|
||||||
@ -90,7 +90,7 @@ export class HTTPClientMock<T extends IRoutes> extends HTTPClient<T> {
|
|||||||
* replaced. The signature is calculated using the `serialize()` method,
|
* replaced. The signature is calculated using the `serialize()` method,
|
||||||
* which just does a `JSON.stringify(req)`.
|
* which just does a `JSON.stringify(req)`.
|
||||||
*/
|
*/
|
||||||
mockAdd(req: IRequest, data: any, status = 200): this {
|
mockAdd(req: Request, data: any, status = 200): this {
|
||||||
this.mocks[this.serialize(req)] = {data, status}
|
this.mocks[this.serialize(req)] = {data, status}
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
@ -98,8 +98,8 @@ export class HTTPClientMock<T extends IRoutes> extends HTTPClient<T> {
|
|||||||
/**
|
/**
|
||||||
* Adds a new mock with predefined type
|
* Adds a new mock with predefined type
|
||||||
*/
|
*/
|
||||||
mockAddTyped<P extends keyof T & string, M extends TMethod>(
|
mockAddTyped<P extends keyof T & string, M extends Method>(
|
||||||
params: ITypedRequestParams<T, P, M>,
|
params: TypedRequestParams<T, P, M>,
|
||||||
response: T[P][M]['response'],
|
response: T[P][M]['response'],
|
||||||
): this {
|
): this {
|
||||||
const url = this.formatter.format(params.path, params.params)
|
const url = this.formatter.format(params.path, params.params)
|
||||||
@ -120,7 +120,7 @@ export class HTTPClientMock<T extends IRoutes> extends HTTPClient<T> {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
protected notify(r: IReqRes | Error) {
|
protected notify(r: ReqRes | Error) {
|
||||||
if (!this.waitPromise) {
|
if (!this.waitPromise) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -147,12 +147,12 @@ export class HTTPClientMock<T extends IRoutes> extends HTTPClient<T> {
|
|||||||
* const {req, res} = await httpMock.wait()
|
* const {req, res} = await httpMock.wait()
|
||||||
* expect(req).toEqual({method:'get', url:'/auth/post', data: {...}})
|
* expect(req).toEqual({method:'get', url:'/auth/post', data: {...}})
|
||||||
*/
|
*/
|
||||||
async wait(): Promise<IReqRes> {
|
async wait(): Promise<ReqRes> {
|
||||||
if (this.requests.every(r => r.finished)) {
|
if (this.requests.every(r => r.finished)) {
|
||||||
throw new Error('No requests to wait for')
|
throw new Error('No requests to wait for')
|
||||||
}
|
}
|
||||||
expect(this.waitPromise).toBe(undefined)
|
expect(this.waitPromise).toBe(undefined)
|
||||||
const result: IReqRes = await new Promise((resolve, reject) => {
|
const result: ReqRes = await new Promise((resolve, reject) => {
|
||||||
this.waitPromise = {resolve, reject}
|
this.waitPromise = {resolve, reject}
|
||||||
})
|
})
|
||||||
// TODO think of a better way to do this.
|
// TODO think of a better way to do this.
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
export interface IHeader {
|
export interface Headers {
|
||||||
readonly [key: string]: string
|
readonly [key: string]: string
|
||||||
}
|
}
|
||||||
@ -1,51 +0,0 @@
|
|||||||
import {TMethod, IRoutes} from '@rondo.dev/http-types'
|
|
||||||
import {ITypedRequestParams} from './ITypedRequestParams'
|
|
||||||
|
|
||||||
export interface IHTTPClient<T extends IRoutes> {
|
|
||||||
request<
|
|
||||||
P extends keyof T & string,
|
|
||||||
M extends TMethod,
|
|
||||||
>(params: ITypedRequestParams<T, P, M>): Promise<T[P][M]['response']>
|
|
||||||
|
|
||||||
get<P extends keyof T & string>(
|
|
||||||
path: P,
|
|
||||||
query?: T[P]['get']['query'],
|
|
||||||
params?: T[P]['get']['params'],
|
|
||||||
): Promise<T[P]['get']['response']>
|
|
||||||
|
|
||||||
post<P extends keyof T & string>(
|
|
||||||
path: P,
|
|
||||||
body: T[P]['post']['body'],
|
|
||||||
params?: T[P]['post']['params'],
|
|
||||||
): Promise<T[P]['post']['response']>
|
|
||||||
|
|
||||||
put<P extends keyof T & string>(
|
|
||||||
path: P,
|
|
||||||
body: T[P]['put']['body'],
|
|
||||||
params?: T[P]['put']['params'],
|
|
||||||
): Promise<T[P]['put']['response']>
|
|
||||||
|
|
||||||
delete<P extends keyof T & string>(
|
|
||||||
path: P,
|
|
||||||
body: T[P]['delete']['body'],
|
|
||||||
params?: T[P]['delete']['params'],
|
|
||||||
): Promise<T[P]['delete']['response']>
|
|
||||||
|
|
||||||
head<P extends keyof T & string>(
|
|
||||||
path: P,
|
|
||||||
query?: T[P]['head']['query'],
|
|
||||||
params?: T[P]['head']['params'],
|
|
||||||
): Promise<T[P]['head']['response']>
|
|
||||||
|
|
||||||
options<P extends keyof T & string>(
|
|
||||||
path: P,
|
|
||||||
query?: T[P]['options']['query'],
|
|
||||||
params?: T[P]['options']['params'],
|
|
||||||
): Promise<T[P]['options']['response']>
|
|
||||||
|
|
||||||
patch<P extends keyof T & string>(
|
|
||||||
path: P,
|
|
||||||
body: T[P]['patch']['body'],
|
|
||||||
params?: T[P]['patch']['params'],
|
|
||||||
): Promise<T[P]['patch']['response']>
|
|
||||||
}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import {TMethod} from '@rondo.dev/http-types'
|
|
||||||
|
|
||||||
export interface IRequest {
|
|
||||||
method: TMethod,
|
|
||||||
url: string,
|
|
||||||
params?: {[key: string]: any},
|
|
||||||
data?: any,
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
import {IRoutes, TMethod} from '@rondo.dev/http-types'
|
|
||||||
|
|
||||||
export interface ITypedRequestParams<
|
|
||||||
T extends IRoutes,
|
|
||||||
P extends keyof T & string,
|
|
||||||
M extends TMethod,
|
|
||||||
> {
|
|
||||||
method: M,
|
|
||||||
path: P,
|
|
||||||
params?: T[P][M]['params'],
|
|
||||||
query?: T[P][M]['query'],
|
|
||||||
body?: T[P][M]['body'],
|
|
||||||
}
|
|
||||||
8
packages/http-client/src/Request.ts
Normal file
8
packages/http-client/src/Request.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import {Method} from '@rondo.dev/http-types'
|
||||||
|
|
||||||
|
export interface Request {
|
||||||
|
method: Method
|
||||||
|
url: string
|
||||||
|
params?: {[key: string]: any}
|
||||||
|
data?: any
|
||||||
|
}
|
||||||
@ -1,3 +1,3 @@
|
|||||||
export interface IRequestQuery {
|
export interface RequestParams {
|
||||||
[key: string]: string | number
|
[key: string]: string | number
|
||||||
}
|
}
|
||||||
@ -1,3 +1,3 @@
|
|||||||
export interface IRequestParams {
|
export interface RequestQuery {
|
||||||
[key: string]: string | number
|
[key: string]: string | number
|
||||||
}
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
export interface IResponse {
|
export interface Response {
|
||||||
data: any
|
data: any
|
||||||
status: number
|
status: number
|
||||||
}
|
}
|
||||||
140
packages/http-client/src/SimpleHTTPClient.ts
Normal file
140
packages/http-client/src/SimpleHTTPClient.ts
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
import {HTTPClient} from './HTTPClient'
|
||||||
|
import {Headers} from './Headers'
|
||||||
|
import {Method, Routes} from '@rondo.dev/http-types'
|
||||||
|
import {URLFormatter} from './URLFormatter'
|
||||||
|
import {Request} from './Request'
|
||||||
|
import {Response} from './Response'
|
||||||
|
import {TypedRequestParams} from './TypedRequestParams'
|
||||||
|
|
||||||
|
interface Requestor {
|
||||||
|
request: (params: Request) => Promise<Response>
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SimpleHTTPClient<T extends Routes> implements HTTPClient<T> {
|
||||||
|
protected readonly requestor: Requestor
|
||||||
|
protected readonly formatter: URLFormatter
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected readonly baseURL = '',
|
||||||
|
protected readonly headers?: Headers,
|
||||||
|
) {
|
||||||
|
this.requestor = this.createRequestor()
|
||||||
|
this.formatter = new URLFormatter()
|
||||||
|
}
|
||||||
|
|
||||||
|
protected createRequestor(): Requestor {
|
||||||
|
return axios.create({
|
||||||
|
baseURL: this.baseURL,
|
||||||
|
headers: this.headers,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async request<
|
||||||
|
P extends keyof T & string,
|
||||||
|
M extends Method,
|
||||||
|
>(params: TypedRequestParams<T, P, M>): Promise<T[P][M]['response']> {
|
||||||
|
|
||||||
|
const url = this.formatter.format(params.path, params.params)
|
||||||
|
|
||||||
|
const response = await this.requestor.request({
|
||||||
|
method: params.method,
|
||||||
|
url,
|
||||||
|
params: params.query,
|
||||||
|
data: params.body,
|
||||||
|
})
|
||||||
|
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
|
||||||
|
get<P extends keyof T & string>(
|
||||||
|
path: P,
|
||||||
|
query?: T[P]['get']['query'],
|
||||||
|
params?: T[P]['get']['params'],
|
||||||
|
) {
|
||||||
|
return this.request({
|
||||||
|
method: 'get',
|
||||||
|
path,
|
||||||
|
query,
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
post<P extends keyof T & string>(
|
||||||
|
path: P,
|
||||||
|
body: T[P]['post']['body'],
|
||||||
|
params?: T[P]['post']['params'],
|
||||||
|
) {
|
||||||
|
return this.request({
|
||||||
|
method: 'post',
|
||||||
|
path,
|
||||||
|
body,
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
put<P extends keyof T & string>(
|
||||||
|
path: P,
|
||||||
|
body: T[P]['put']['body'],
|
||||||
|
params?: T[P]['put']['params'],
|
||||||
|
) {
|
||||||
|
return this.request({
|
||||||
|
method: 'put',
|
||||||
|
path,
|
||||||
|
body,
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
delete<P extends keyof T & string>(
|
||||||
|
path: P,
|
||||||
|
body: T[P]['delete']['body'],
|
||||||
|
params?: T[P]['delete']['params'],
|
||||||
|
) {
|
||||||
|
return this.request({
|
||||||
|
method: 'delete',
|
||||||
|
path,
|
||||||
|
body,
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
head<P extends keyof T & string>(
|
||||||
|
path: P,
|
||||||
|
query?: T[P]['head']['query'],
|
||||||
|
params?: T[P]['head']['params'],
|
||||||
|
) {
|
||||||
|
return this.request({
|
||||||
|
method: 'head',
|
||||||
|
path,
|
||||||
|
params,
|
||||||
|
query,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
options<P extends keyof T & string>(
|
||||||
|
path: P,
|
||||||
|
query?: T[P]['options']['query'],
|
||||||
|
params?: T[P]['options']['params'],
|
||||||
|
) {
|
||||||
|
return this.request({
|
||||||
|
method: 'options',
|
||||||
|
path,
|
||||||
|
params,
|
||||||
|
query,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
patch<P extends keyof T & string>(
|
||||||
|
path: P,
|
||||||
|
body: T[P]['patch']['body'],
|
||||||
|
params?: T[P]['patch']['params'],
|
||||||
|
) {
|
||||||
|
return this.request({
|
||||||
|
method: 'patch',
|
||||||
|
path,
|
||||||
|
body,
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
13
packages/http-client/src/TypedRequestParams.ts
Normal file
13
packages/http-client/src/TypedRequestParams.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import {Routes, Method} from '@rondo.dev/http-types'
|
||||||
|
|
||||||
|
export interface TypedRequestParams<
|
||||||
|
T extends Routes,
|
||||||
|
P extends keyof T & string,
|
||||||
|
M extends Method,
|
||||||
|
> {
|
||||||
|
method: M
|
||||||
|
path: P
|
||||||
|
params?: T[P][M]['params']
|
||||||
|
query?: T[P][M]['query']
|
||||||
|
body?: T[P][M]['body']
|
||||||
|
}
|
||||||
@ -1,27 +1,27 @@
|
|||||||
import {IRequestParams} from './IRequestParams'
|
import {RequestParams} from './RequestParams'
|
||||||
import {IRequestQuery} from './IRequestQuery'
|
import {RequestQuery} from './RequestQuery'
|
||||||
|
|
||||||
export interface IURLFormatterOptions {
|
export interface URLFormatterOptions {
|
||||||
readonly baseURL: string
|
readonly baseURL: string
|
||||||
readonly regex: RegExp
|
readonly regex: RegExp
|
||||||
}
|
}
|
||||||
|
|
||||||
export class URLFormatter {
|
export class URLFormatter {
|
||||||
constructor(readonly params: IURLFormatterOptions = {
|
constructor(readonly params: URLFormatterOptions = {
|
||||||
baseURL: '',
|
baseURL: '',
|
||||||
regex: /:[a-zA-Z0-9-]+/g,
|
regex: /:[a-zA-Z0-9-]+/g,
|
||||||
}) {}
|
}) {}
|
||||||
|
|
||||||
format(
|
format(
|
||||||
url: string,
|
url: string,
|
||||||
params?: IRequestParams,
|
params?: RequestParams,
|
||||||
query?: IRequestQuery,
|
query?: RequestQuery,
|
||||||
) {
|
) {
|
||||||
let formattedUrl = url
|
let formattedUrl = url
|
||||||
if (params) {
|
if (params) {
|
||||||
formattedUrl = url.replace(this.params.regex, match => {
|
formattedUrl = url.replace(this.params.regex, match => {
|
||||||
const key = match.substring(1)
|
const key = match.substring(1)
|
||||||
if (!params.hasOwnProperty(key)) {
|
if (!Object.prototype.hasOwnProperty.call(params, key)) {
|
||||||
throw new Error('Undefined URL paramter: ' + key)
|
throw new Error('Undefined URL paramter: ' + key)
|
||||||
}
|
}
|
||||||
return String(params![key])
|
return String(params![key])
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
export * from './HTTPClient'
|
export * from './HTTPClient'
|
||||||
export * from './HTTPClientMock'
|
export * from './HTTPClientMock'
|
||||||
export * from './IHeader'
|
export * from './Headers'
|
||||||
export * from './IHTTPClient'
|
export * from './HTTPClient'
|
||||||
export * from './IRequest'
|
export * from './Request'
|
||||||
export * from './IRequestParams'
|
export * from './RequestParams'
|
||||||
export * from './IRequestQuery'
|
export * from './RequestQuery'
|
||||||
export * from './IResponse'
|
export * from './Response'
|
||||||
export * from './ITypedRequestParams'
|
export * from './TypedRequestParams'
|
||||||
export * from './URLFormatter'
|
export * from './URLFormatter'
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
export type TMethod = 'get'
|
/* eslint @typescript-eslint/no-explicit-any: 0 */
|
||||||
|
|
||||||
|
export type Method = 'get'
|
||||||
| 'post'
|
| 'post'
|
||||||
| 'put'
|
| 'put'
|
||||||
| 'delete'
|
| 'delete'
|
||||||
@ -6,14 +8,14 @@ export type TMethod = 'get'
|
|||||||
| 'head'
|
| 'head'
|
||||||
| 'options'
|
| 'options'
|
||||||
|
|
||||||
export interface IRoutes {
|
export interface Routes {
|
||||||
// has to be any because otherwise TypeScript will start
|
// has to be any because otherwise TypeScript will start
|
||||||
// throwing error and interfaces without an index signature
|
// throwing error and interfaces without an index signature
|
||||||
// would not be usable
|
// would not be usable
|
||||||
[route: string]: any
|
[route: string]: any
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IRoute {
|
export interface Route {
|
||||||
params: any
|
params: any
|
||||||
query: any
|
query: any
|
||||||
body: any
|
body: any
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
|
|
||||||
function attachErrorListener(
|
function attachErrorListener(
|
||||||
reader: FileReader,
|
reader: FileReader,
|
||||||
file: File,
|
file: File,
|
||||||
reject: (err: Error) => void,
|
reject: (err: Error) => void,
|
||||||
) {
|
) {
|
||||||
reader.onerror = ev => reject(new Error('Error reading file: ' +
|
reader.onerror = () => reject(new Error('Error reading file: ' +
|
||||||
(reader.error ? reader.error.message : file.name)))
|
(reader.error ? reader.error.message : file.name)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
export async function createImageFromDataURL(dataURL: string)
|
export async function createImageFromDataURL(
|
||||||
: Promise<HTMLImageElement> {
|
dataURL: string,
|
||||||
|
): Promise<HTMLImageElement> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const image = new Image()
|
const image = new Image()
|
||||||
image.onload = () => resolve(image)
|
image.onload = () => resolve(image)
|
||||||
@ -8,8 +9,9 @@ export async function createImageFromDataURL(dataURL: string)
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function drawCanvasFromDataURL(dataURL: string)
|
export async function drawCanvasFromDataURL(
|
||||||
: Promise<HTMLCanvasElement> {
|
dataURL: string,
|
||||||
|
): Promise<HTMLCanvasElement> {
|
||||||
const canvas = document.createElement('canvas')
|
const canvas = document.createElement('canvas')
|
||||||
const ctx = canvas.getContext('2d')!
|
const ctx = canvas.getContext('2d')!
|
||||||
const image = await createImageFromDataURL(dataURL)
|
const image = await createImageFromDataURL(dataURL)
|
||||||
@ -24,7 +26,7 @@ export async function getCanvasFromArrayBuffer(
|
|||||||
width: number,
|
width: number,
|
||||||
height: number,
|
height: number,
|
||||||
) {
|
) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise(() => {
|
||||||
const canvas = document.createElement('canvas')
|
const canvas = document.createElement('canvas')
|
||||||
const ctx = canvas.getContext('2d')!
|
const ctx = canvas.getContext('2d')!
|
||||||
const imageData = new ImageData(
|
const imageData = new ImageData(
|
||||||
|
|||||||
@ -3,34 +3,33 @@ import {Resizer} from './Resizer'
|
|||||||
import {readAsDataURL} from './Files'
|
import {readAsDataURL} from './Files'
|
||||||
import {drawCanvasFromDataURL} from './Image'
|
import {drawCanvasFromDataURL} from './Image'
|
||||||
|
|
||||||
export interface IImage {
|
export interface Image {
|
||||||
dataURL: string
|
dataURL: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IImageUploadProps {
|
export interface ImageUploadProps {
|
||||||
onChange: (images: IImage[]) => void
|
onChange: (images: Image[]) => void
|
||||||
multiple: boolean
|
multiple: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ImageUpload extends React.PureComponent<IImageUploadProps> {
|
export class ImageUpload extends React.PureComponent<ImageUploadProps> {
|
||||||
fileInput: React.RefObject<HTMLInputElement>
|
fileInput: React.RefObject<HTMLInputElement>
|
||||||
constructor(props: IImageUploadProps) {
|
constructor(props: ImageUploadProps) {
|
||||||
super(props)
|
super(props)
|
||||||
this.fileInput = React.createRef()
|
this.fileInput = React.createRef()
|
||||||
}
|
}
|
||||||
|
|
||||||
safeHandleChange = async (event: React.SyntheticEvent<HTMLInputElement>) => {
|
safeHandleChange = async () => {
|
||||||
try {
|
try {
|
||||||
await this.handleChange(event)
|
await this.handleChange()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// console.log('Error in handleChange', err)
|
// console.log('Error in handleChange', err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handleChange = async (event: React.SyntheticEvent<HTMLInputElement>) => {
|
handleChange = async () => {
|
||||||
const self = this
|
|
||||||
const files = Array.from(this.fileInput.current!.files!)
|
const files = Array.from(this.fileInput.current!.files!)
|
||||||
|
|
||||||
const resized: IImage[] = []
|
const resized: Image[] = []
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const dataURL = await readAsDataURL(file)
|
const dataURL = await readAsDataURL(file)
|
||||||
const resizedDataURL = await this.resize(dataURL)
|
const resizedDataURL = await this.resize(dataURL)
|
||||||
@ -44,7 +43,7 @@ export class ImageUpload extends React.PureComponent<IImageUploadProps> {
|
|||||||
this.fileInput.current!.parentElement!.appendChild(img)
|
this.fileInput.current!.parentElement!.appendChild(img)
|
||||||
}
|
}
|
||||||
|
|
||||||
(window as any).resized = resized
|
// (window as any).resized = resized
|
||||||
this.props.onChange(resized)
|
this.props.onChange(resized)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
interface IWorkerParam {
|
interface WorkerParam {
|
||||||
sourceWidth: number
|
sourceWidth: number
|
||||||
sourceHeight: number
|
sourceHeight: number
|
||||||
width: number
|
width: number
|
||||||
@ -6,21 +6,22 @@ interface IWorkerParam {
|
|||||||
source: ArrayBuffer
|
source: ArrayBuffer
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IWorkerParamMessage {
|
interface WorkerParamMessage {
|
||||||
data: IWorkerParam
|
data: WorkerParam
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IWorkerResult {
|
interface WorkerResult {
|
||||||
target: Uint8ClampedArray
|
target: Uint8ClampedArray
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IWorkerResultMessage {
|
interface WorkerResultMessage {
|
||||||
data: IWorkerResult
|
data: WorkerResult
|
||||||
}
|
}
|
||||||
|
|
||||||
function createResizeWorker(root: any = {})
|
function createResizeWorker(
|
||||||
: {onmessage: (event: IWorkerParamMessage) => void} {
|
root: any = {}, // eslint-disable-line
|
||||||
root.onmessage = (event: IWorkerParamMessage) => {
|
): {onmessage: (event: WorkerParamMessage) => void} {
|
||||||
|
root.onmessage = (event: WorkerParamMessage) => {
|
||||||
const sourceWidth = event.data.sourceWidth
|
const sourceWidth = event.data.sourceWidth
|
||||||
const sourceHeight = event.data.sourceHeight
|
const sourceHeight = event.data.sourceHeight
|
||||||
const width = event.data.width
|
const width = event.data.width
|
||||||
@ -32,7 +33,7 @@ function createResizeWorker(root: any = {})
|
|||||||
const ratioHalfH = Math.ceil(ratioH / 2)
|
const ratioHalfH = Math.ceil(ratioH / 2)
|
||||||
|
|
||||||
const source = new Uint8ClampedArray(event.data.source)
|
const source = new Uint8ClampedArray(event.data.source)
|
||||||
const sourceH = source.length / sourceWidth / 4
|
// const sourceH = source.length / sourceWidth / 4
|
||||||
const targetSize = width * height * 4
|
const targetSize = width * height * 4
|
||||||
const targetMemory = new ArrayBuffer(targetSize)
|
const targetMemory = new ArrayBuffer(targetSize)
|
||||||
const target = new Uint8ClampedArray(targetMemory, 0, targetSize)
|
const target = new Uint8ClampedArray(targetMemory, 0, targetSize)
|
||||||
@ -92,10 +93,10 @@ function createResizeWorker(root: any = {})
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const objData: IWorkerResult = {
|
const objData: WorkerResult = {
|
||||||
target,
|
target,
|
||||||
}
|
}
|
||||||
postMessage(objData, [target.buffer] as any)
|
postMessage(objData, [target.buffer] as any) // eslint-disable-line
|
||||||
}
|
}
|
||||||
return root
|
return root
|
||||||
}
|
}
|
||||||
@ -156,7 +157,7 @@ export class Resizer {
|
|||||||
const worker = new Worker(this.workerBlobURL)
|
const worker = new Worker(this.workerBlobURL)
|
||||||
activeWorkers += 1
|
activeWorkers += 1
|
||||||
workers[c] = worker
|
workers[c] = worker
|
||||||
worker.onmessage = (event: IWorkerResultMessage) => {
|
worker.onmessage = (event: WorkerResultMessage) => {
|
||||||
worker.terminate()
|
worker.terminate()
|
||||||
delete workers[c]
|
delete workers[c]
|
||||||
activeWorkers -= 1
|
activeWorkers -= 1
|
||||||
@ -174,7 +175,7 @@ export class Resizer {
|
|||||||
reject(new Error('Error resizing: ' + err.message))
|
reject(new Error('Error resizing: ' + err.message))
|
||||||
}
|
}
|
||||||
|
|
||||||
const message: IWorkerParam = {
|
const message: WorkerParam = {
|
||||||
sourceWidth,
|
sourceWidth,
|
||||||
sourceHeight: partition.height,
|
sourceHeight: partition.height,
|
||||||
width,
|
width,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user