11import config from "../../config.js" ;
2+ import createClient , { FetchOptions , Middleware } from "openapi-fetch" ;
23
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" ;
145
156export interface OAuthToken {
167 access_token : string ;
@@ -40,6 +31,16 @@ export class ApiClientError extends Error {
4031 this . name = "ApiClientError" ;
4132 this . response = response ;
4233 }
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+ }
4344}
4445
4546export interface ApiClientOptions {
@@ -48,32 +49,40 @@ export interface ApiClientOptions {
4849}
4950
5051export 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+ } ) ;
5379
5480 constructor ( options : ApiClientOptions ) {
5581 const { token, saveToken } = options ;
5682 this . token = token ;
5783 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 ( ) ) ;
7786 }
7887
7988 async storeToken ( token : OAuthToken ) : Promise < OAuthToken > {
@@ -86,36 +95,6 @@ export class ApiClient {
8695 return token ;
8796 }
8897
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-
11998 async authenticate ( ) : Promise < OauthDeviceCode > {
12099 const endpoint = "api/private/unauth/account/device/authorize" ;
121100
@@ -135,7 +114,7 @@ export class ApiClient {
135114 } ) ;
136115
137116 if ( ! response . ok ) {
138- throw new ApiClientError ( `Failed to initiate authentication: ${ response . statusText } ` , response ) ;
117+ throw await ApiClientError . fromResponse ( response , `failed to initiate authentication` ) ;
139118 }
140119
141120 return ( await response . json ( ) ) as OauthDeviceCode ;
@@ -166,14 +145,18 @@ export class ApiClient {
166145 try {
167146 const errorResponse = await response . json ( ) ;
168147 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." ) ;
172149 } 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+ ) ;
174154 }
175155 } 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+ ) ;
177160 }
178161 }
179162
@@ -195,7 +178,7 @@ export class ApiClient {
195178 } ) ;
196179
197180 if ( ! response . ok ) {
198- throw new ApiClientError ( ` Failed to refresh token: ${ response . statusText } ` , response ) ;
181+ throw await ApiClientError . fromResponse ( response , " Failed to refresh token" ) ;
199182 }
200183 const data = await response . json ( ) ;
201184
@@ -229,7 +212,7 @@ export class ApiClient {
229212 } ) ;
230213
231214 if ( ! response . ok ) {
232- throw new ApiClientError ( `Failed to revoke token: ${ response . statusText } ` , response ) ;
215+ throw await ApiClientError . fromResponse ( response ) ;
233216 }
234217
235218 if ( ! token && this . token ) {
@@ -269,58 +252,53 @@ export class ApiClient {
269252 }
270253 }
271254
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 ;
274258 }
275259
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 ;
278263 }
279264
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 ;
288268 }
289269
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 ;
292273 }
293274
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 ;
296278 }
297279
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 ;
300283 }
301284
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 ;
304288 }
305289
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 ;
314293 }
315294
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 ;
321298 }
322299
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 ;
325303 }
326304}
0 commit comments