1
1
import config from "../../config.js" ;
2
+ import createClient , { FetchOptions , Middleware } from "openapi-fetch" ;
2
3
3
- import {
4
- Group ,
5
- PaginatedOrgGroupView ,
6
- PaginatedAtlasGroupView ,
7
- ClusterDescription20240805 ,
8
- PaginatedClusterDescription20240805 ,
9
- PaginatedNetworkAccessView ,
10
- NetworkPermissionEntry ,
11
- CloudDatabaseUser ,
12
- PaginatedApiAtlasDatabaseUserView ,
13
- } from "./openapi.js" ;
4
+ import { paths , operations } from "./openapi.js" ;
14
5
15
6
export interface OAuthToken {
16
7
access_token : string ;
@@ -40,6 +31,16 @@ export class ApiClientError extends Error {
40
31
this . name = "ApiClientError" ;
41
32
this . response = response ;
42
33
}
34
+
35
+ static async fromResponse ( response : Response , message ?: string ) : Promise < ApiClientError > {
36
+ message ||= `error calling Atlas API` ;
37
+ try {
38
+ const text = await response . text ( ) ;
39
+ return new ApiClientError ( `${ message } : [${ response . status } ${ response . statusText } ] ${ text } ` , response ) ;
40
+ } catch {
41
+ return new ApiClientError ( `${ message } : ${ response . status } ${ response . statusText } ` , response ) ;
42
+ }
43
+ }
43
44
}
44
45
45
46
export interface ApiClientOptions {
@@ -48,32 +49,40 @@ export interface ApiClientOptions {
48
49
}
49
50
50
51
export class ApiClient {
51
- token ?: OAuthToken ;
52
- saveToken ?: saveTokenFunction ;
52
+ private token ?: OAuthToken ;
53
+ private saveToken ?: saveTokenFunction ;
54
+ private client = createClient < paths > ( {
55
+ baseUrl : config . apiBaseUrl ,
56
+ headers : {
57
+ "User-Agent" : config . userAgent ,
58
+ Accept : `application/vnd.atlas.${ config . atlasApiVersion } +json` ,
59
+ } ,
60
+ } ) ;
61
+ private authMiddleware = ( apiClient : ApiClient ) : Middleware => ( {
62
+ async onRequest ( { request, schemaPath } ) {
63
+ if ( schemaPath . startsWith ( "/api/private/unauth" ) || schemaPath . startsWith ( "/api/oauth" ) ) {
64
+ return undefined ;
65
+ }
66
+ if ( await apiClient . validateToken ( ) ) {
67
+ request . headers . set ( "Authorization" , `Bearer ${ apiClient . token ?. access_token } ` ) ;
68
+ return request ;
69
+ }
70
+ } ,
71
+ } ) ;
72
+ private errorMiddleware = ( ) : Middleware => ( {
73
+ async onResponse ( { response } ) {
74
+ if ( ! response . ok ) {
75
+ throw await ApiClientError . fromResponse ( response ) ;
76
+ }
77
+ } ,
78
+ } ) ;
53
79
54
80
constructor ( options : ApiClientOptions ) {
55
81
const { token, saveToken } = options ;
56
82
this . token = token ;
57
83
this . saveToken = saveToken ;
58
- }
59
-
60
- private defaultOptions ( ) : RequestInit {
61
- const authHeaders = ! this . token ?. access_token
62
- ? null
63
- : {
64
- Authorization : `Bearer ${ this . token . access_token } ` ,
65
- } ;
66
-
67
- return {
68
- method : "GET" ,
69
- credentials : ! this . token ?. access_token ? undefined : "include" ,
70
- headers : {
71
- "Content-Type" : "application/json" ,
72
- Accept : `application/vnd.atlas.${ config . atlasApiVersion } +json` ,
73
- "User-Agent" : config . userAgent ,
74
- ...authHeaders ,
75
- } ,
76
- } ;
84
+ this . client . use ( this . authMiddleware ( this ) ) ;
85
+ this . client . use ( this . errorMiddleware ( ) ) ;
77
86
}
78
87
79
88
async storeToken ( token : OAuthToken ) : Promise < OAuthToken > {
@@ -86,36 +95,6 @@ export class ApiClient {
86
95
return token ;
87
96
}
88
97
89
- async do < T > ( endpoint : string , options ?: RequestInit ) : Promise < T > {
90
- if ( ! this . token || ! this . token . access_token ) {
91
- throw new Error ( "Not authenticated. Please run the auth tool first." ) ;
92
- }
93
-
94
- const url = new URL ( `api/atlas/v2${ endpoint } ` , `${ config . apiBaseUrl } ` ) ;
95
-
96
- if ( ! this . checkTokenExpiry ( ) ) {
97
- await this . refreshToken ( ) ;
98
- }
99
-
100
- const defaultOpt = this . defaultOptions ( ) ;
101
- const opt = {
102
- ...defaultOpt ,
103
- ...options ,
104
- headers : {
105
- ...defaultOpt . headers ,
106
- ...options ?. headers ,
107
- } ,
108
- } ;
109
-
110
- const response = await fetch ( url , opt ) ;
111
-
112
- if ( ! response . ok ) {
113
- throw new ApiClientError ( `Error calling Atlas API: ${ await response . text ( ) } ` , response ) ;
114
- }
115
-
116
- return ( await response . json ( ) ) as T ;
117
- }
118
-
119
98
async authenticate ( ) : Promise < OauthDeviceCode > {
120
99
const endpoint = "api/private/unauth/account/device/authorize" ;
121
100
@@ -135,7 +114,7 @@ export class ApiClient {
135
114
} ) ;
136
115
137
116
if ( ! response . ok ) {
138
- throw new ApiClientError ( `Failed to initiate authentication: ${ response . statusText } ` , response ) ;
117
+ throw await ApiClientError . fromResponse ( response , `failed to initiate authentication` ) ;
139
118
}
140
119
141
120
return ( await response . json ( ) ) as OauthDeviceCode ;
@@ -166,14 +145,18 @@ export class ApiClient {
166
145
try {
167
146
const errorResponse = await response . json ( ) ;
168
147
if ( errorResponse . errorCode === "DEVICE_AUTHORIZATION_PENDING" ) {
169
- throw new ApiClientError ( "Authentication pending. Try again later." , response ) ;
170
- } else if ( errorResponse . error === "expired_token" ) {
171
- throw new ApiClientError ( "Device code expired. Please restart the authentication process." , response ) ;
148
+ throw await ApiClientError . fromResponse ( response , "Authentication pending. Try again later." ) ;
172
149
} else {
173
- throw new ApiClientError ( "Device code expired. Please restart the authentication process." , response ) ;
150
+ throw await ApiClientError . fromResponse (
151
+ response ,
152
+ "Device code expired. Please restart the authentication process."
153
+ ) ;
174
154
}
175
155
} catch {
176
- throw new ApiClientError ( "Failed to retrieve token. Please check your device code." , response ) ;
156
+ throw await ApiClientError . fromResponse (
157
+ response ,
158
+ "Failed to retrieve token. Please check your device code."
159
+ ) ;
177
160
}
178
161
}
179
162
@@ -195,7 +178,7 @@ export class ApiClient {
195
178
} ) ;
196
179
197
180
if ( ! response . ok ) {
198
- throw new ApiClientError ( ` Failed to refresh token: ${ response . statusText } ` , response ) ;
181
+ throw await ApiClientError . fromResponse ( response , " Failed to refresh token" ) ;
199
182
}
200
183
const data = await response . json ( ) ;
201
184
@@ -229,7 +212,7 @@ export class ApiClient {
229
212
} ) ;
230
213
231
214
if ( ! response . ok ) {
232
- throw new ApiClientError ( `Failed to revoke token: ${ response . statusText } ` , response ) ;
215
+ throw await ApiClientError . fromResponse ( response ) ;
233
216
}
234
217
235
218
if ( ! token && this . token ) {
@@ -269,58 +252,53 @@ export class ApiClient {
269
252
}
270
253
}
271
254
272
- async listProjects ( ) : Promise < PaginatedAtlasGroupView > {
273
- return await this . do < PaginatedAtlasGroupView > ( "/groups" ) ;
255
+ async listProjects ( options ?: FetchOptions < operations [ "listProjects" ] > ) {
256
+ const { data } = await this . client . GET ( `/api/atlas/v2/groups` , options ) ;
257
+ return data ;
274
258
}
275
259
276
- async listProjectIpAccessLists ( groupId : string ) : Promise < PaginatedNetworkAccessView > {
277
- return await this . do < PaginatedNetworkAccessView > ( `/groups/${ groupId } /accessList` ) ;
260
+ async listProjectIpAccessLists ( options : FetchOptions < operations [ "listProjectIpAccessLists" ] > ) {
261
+ const { data } = await this . client . GET ( `/api/atlas/v2/groups/{groupId}/accessList` , options ) ;
262
+ return data ;
278
263
}
279
264
280
- async createProjectIpAccessList (
281
- groupId : string ,
282
- entries : NetworkPermissionEntry [ ]
283
- ) : Promise < PaginatedNetworkAccessView > {
284
- return await this . do < PaginatedNetworkAccessView > ( `/groups/${ groupId } /accessList` , {
285
- method : "POST" ,
286
- body : JSON . stringify ( entries ) ,
287
- } ) ;
265
+ async createProjectIpAccessList ( options : FetchOptions < operations [ "createProjectIpAccessList" ] > ) {
266
+ const { data } = await this . client . POST ( `/api/atlas/v2/groups/{groupId}/accessList` , options ) ;
267
+ return data ;
288
268
}
289
269
290
- async getProject ( groupId : string ) : Promise < Group > {
291
- return await this . do < Group > ( `/groups/${ groupId } ` ) ;
270
+ async getProject ( options : FetchOptions < operations [ "getProject" ] > ) {
271
+ const { data } = await this . client . GET ( `/api/atlas/v2/groups/{groupId}` , options ) ;
272
+ return data ;
292
273
}
293
274
294
- async listClusters ( groupId : string ) : Promise < PaginatedClusterDescription20240805 > {
295
- return await this . do < PaginatedClusterDescription20240805 > ( `/groups/${ groupId } /clusters` ) ;
275
+ async listClusters ( options : FetchOptions < operations [ "listClusters" ] > ) {
276
+ const { data } = await this . client . GET ( `/api/atlas/v2/groups/{groupId}/clusters` , options ) ;
277
+ return data ;
296
278
}
297
279
298
- async listClustersForAllProjects ( ) : Promise < PaginatedOrgGroupView > {
299
- return await this . do < PaginatedOrgGroupView > ( `/clusters` ) ;
280
+ async listClustersForAllProjects ( options ?: FetchOptions < operations [ "listClustersForAllProjects" ] > ) {
281
+ const { data } = await this . client . GET ( `/api/atlas/v2/clusters` , options ) ;
282
+ return data ;
300
283
}
301
284
302
- async getCluster ( groupId : string , clusterName : string ) : Promise < ClusterDescription20240805 > {
303
- return await this . do < ClusterDescription20240805 > ( `/groups/${ groupId } /clusters/${ clusterName } ` ) ;
285
+ async getCluster ( options : FetchOptions < operations [ "getCluster" ] > ) {
286
+ const { data } = await this . client . GET ( `/api/atlas/v2/groups/{groupId}/clusters/{clusterName}` , options ) ;
287
+ return data ;
304
288
}
305
289
306
- async createCluster ( groupId : string , cluster : ClusterDescription20240805 ) : Promise < ClusterDescription20240805 > {
307
- if ( ! cluster . groupId ) {
308
- throw new Error ( "Cluster groupId is required" ) ;
309
- }
310
- return await this . do < ClusterDescription20240805 > ( `/groups/${ groupId } /clusters` , {
311
- method : "POST" ,
312
- body : JSON . stringify ( cluster ) ,
313
- } ) ;
290
+ async createCluster ( options : FetchOptions < operations [ "createCluster" ] > ) {
291
+ const { data } = await this . client . POST ( "/api/atlas/v2/groups/{groupId}/clusters" , options ) ;
292
+ return data ;
314
293
}
315
294
316
- async createDatabaseUser ( groupId : string , user : CloudDatabaseUser ) : Promise < CloudDatabaseUser > {
317
- return await this . do < CloudDatabaseUser > ( `/groups/${ groupId } /databaseUsers` , {
318
- method : "POST" ,
319
- body : JSON . stringify ( user ) ,
320
- } ) ;
295
+ async createDatabaseUser ( options : FetchOptions < operations [ "createDatabaseUser" ] > ) {
296
+ const { data } = await this . client . POST ( "/api/atlas/v2/groups/{groupId}/databaseUsers" , options ) ;
297
+ return data ;
321
298
}
322
299
323
- async listDatabaseUsers ( groupId : string ) : Promise < PaginatedApiAtlasDatabaseUserView > {
324
- return await this . do < PaginatedApiAtlasDatabaseUserView > ( `/groups/${ groupId } /databaseUsers` ) ;
300
+ async listDatabaseUsers ( options : FetchOptions < operations [ "listDatabaseUsers" ] > ) {
301
+ const { data } = await this . client . GET ( `/api/atlas/v2/groups/{groupId}/databaseUsers` , options ) ;
302
+ return data ;
325
303
}
326
304
}
0 commit comments