Skip to content

Commit

Permalink
refactor: extract API client logic to a separate class
Browse files Browse the repository at this point in the history
  • Loading branch information
evermake committed Mar 31, 2024
1 parent 1b65b31 commit 7cd1c11
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 68 deletions.
77 changes: 9 additions & 68 deletions backend/src/services/sport/client.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
import axios from 'axios'
import { z } from 'zod'
import type { AxiosInstance } from 'axios'
import type { TrainingDetailed, TrainingInfo } from './types'
import { CalendarTraining, FitnessTestResult, Training } from './schemas'
import type { Logger } from '~/utils/logging'
import { ApiClient } from '~/utils/api-client'

/**
* InnoSport API client implementation.
*
* @see https://sugar-slash-7c8.notion.site/Sport-API-9e021517b5664582ae72cd22e02f4cb6
*/
export class SportClient {
private logger: Logger
private axios: AxiosInstance

export class SportClient extends ApiClient {
constructor({
logger,
baseUrl,
Expand All @@ -23,48 +19,15 @@ export class SportClient {
baseUrl: string
token: string
}) {
const axiosInstance = axios.create({
baseURL: baseUrl,
headers: {
Authorization: `Bearer ${token}`,
super({
logger: logger,
axiosOptions: {
baseURL: baseUrl,
headers: {
Authorization: `Bearer ${token}`,
},
},
})

axiosInstance.interceptors.request.use(
(config) => {
logger.debug({
msg: 'API request initiated',
config: {
auth: config.auth,
baseURL: config.baseURL,
data: config.data,
headers: config.headers,
url: config.url,
},
})
return config
},
)

axiosInstance.interceptors.response.use(
(response) => {
logger.debug({
msg: 'API request finished',
response: response,
})
return response
},
(error) => {
logger.error({
msg: 'API request failed',
error: error,
})
return Promise.reject(error)
},
)

this.logger = logger
this.axios = axiosInstance
}

public async getAllSemesters() {
Expand Down Expand Up @@ -227,26 +190,4 @@ export class SportClient {
responseSchema: z.array(FitnessTestResult),
})
}

private async request<S extends z.ZodSchema>({
method,
path,
responseSchema,
queryParams,
data,
}: {
method: 'GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH'
path: string
responseSchema: S
queryParams?: any
data?: any
}): Promise<z.infer<S>> {
const response = await this.axios.request({
method: method,
url: path,
data: data,
params: queryParams,
})
return responseSchema.parse(response.data)
}
}
91 changes: 91 additions & 0 deletions backend/src/utils/api-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import type { z } from 'zod'
import axios from 'axios'
import type { AxiosInstance } from 'axios'
import type { Logger } from './logging'

export class ApiClient {
protected logger: Logger
protected axios: AxiosInstance

constructor({
logger,
axiosOptions,
}: {
logger: Logger
axiosOptions?: Parameters<typeof axios.create>[0]
}) {
this.logger = logger
this.axios = createAxiosWithLogging({
logger: logger,
options: axiosOptions,
})
}

protected async request<S extends z.ZodSchema>(options: (
& Omit<Parameters<typeof axios.request>[0], 'method' | 'url' | 'params'>
& {
method: 'GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH'
path: string
queryParams?: any
responseSchema: S
}
)): Promise<z.infer<S>> {
const {
responseSchema,
path,
queryParams,
...rest
} = options
const response = await this.axios.request({
url: path,
params: queryParams,
...rest,
})
return responseSchema.parse(response.data)
}
}

function createAxiosWithLogging({
logger,
options,
}: {
logger: Logger
options?: Parameters<typeof axios.create>[0]
}): AxiosInstance {
const instance = axios.create(options)

instance.interceptors.request.use(
(config) => {
logger.debug({
msg: 'API request initiated',
config: {
auth: config.auth,
baseURL: config.baseURL,
data: config.data,
headers: config.headers,
url: config.url,
},
})
return config
},
)

instance.interceptors.response.use(
(response) => {
logger.debug({
msg: 'API request finished',
response: response,
})
return response
},
(error) => {
logger.error({
msg: 'API request failed',
error: error,
})
return Promise.reject(error)
},
)

return instance
}

0 comments on commit 7cd1c11

Please sign in to comment.