-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathapi.ts
More file actions
136 lines (121 loc) · 3.27 KB
/
api.ts
File metadata and controls
136 lines (121 loc) · 3.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import axios, { AxiosError } from 'axios';
import { z } from 'zod';
import { analytics } from '../utils/analytics';
import { WIZARD_USER_AGENT } from './constants';
export const ApiUserSchema = z.object({
distinct_id: z.string(),
organizations: z.array(
z.object({
id: z.string().uuid(),
}),
),
team: z.object({
id: z.number(),
organization: z.string().uuid(),
}),
organization: z.object({
id: z.string().uuid(),
}),
});
export const ApiProjectSchema = z.object({
id: z.number(),
uuid: z.string().uuid(),
organization: z.string().uuid(),
api_token: z.string(),
name: z.string(),
});
export type ApiUser = z.infer<typeof ApiUserSchema>;
export type ApiProject = z.infer<typeof ApiProjectSchema>;
class ApiError extends Error {
constructor(
message: string,
public readonly statusCode?: number,
public readonly endpoint?: string,
) {
super(message);
this.name = 'ApiError';
}
}
export async function fetchUserData(
accessToken: string,
baseUrl: string,
): Promise<ApiUser> {
try {
const response = await axios.get(`${baseUrl}/api/users/@me/`, {
headers: {
Authorization: `Bearer ${accessToken}`,
'User-Agent': WIZARD_USER_AGENT,
},
});
return ApiUserSchema.parse(response.data);
} catch (error) {
const apiError = handleApiError(error, 'fetch user data');
analytics.captureException(apiError, {
endpoint: '/api/users/@me/',
baseUrl,
});
throw apiError;
}
}
export async function fetchProjectData(
accessToken: string,
projectId: number,
baseUrl: string,
): Promise<ApiProject> {
try {
const response = await axios.get(`${baseUrl}/api/projects/${projectId}/`, {
headers: {
Authorization: `Bearer ${accessToken}`,
'User-Agent': WIZARD_USER_AGENT,
},
});
return ApiProjectSchema.parse(response.data);
} catch (error) {
const apiError = handleApiError(error, 'fetch project data');
analytics.captureException(apiError, {
endpoint: `/api/projects/${projectId}/`,
baseUrl,
projectId,
});
throw apiError;
}
}
function handleApiError(error: unknown, operation: string): ApiError {
if (axios.isAxiosError(error)) {
const axiosError = error as AxiosError<{ detail?: string }>;
const status = axiosError.response?.status;
const detail = axiosError.response?.data?.detail;
const endpoint = axiosError.config?.url;
if (status === 401) {
return new ApiError(
`Authentication failed while trying to ${operation}`,
status,
endpoint,
);
}
if (status === 403) {
return new ApiError(
`Access denied while trying to ${operation}`,
status,
endpoint,
);
}
if (status === 404) {
return new ApiError(
`Resource not found while trying to ${operation}`,
status,
endpoint,
);
}
const message = detail || `Failed to ${operation}`;
return new ApiError(message, status, endpoint);
}
if (error instanceof z.ZodError) {
return new ApiError(`Invalid response format while trying to ${operation}`);
}
return new ApiError(
`Unexpected error while trying to ${operation}: ${
error instanceof Error ? error.message : 'Unknown error'
}`,
);
}