eletrotupi / tcc/ frontend/lib/api/client.ts4ef759a7c84b0f1067b873dcf561cc752569a817
7.0 KB | 272 lines Raw
1
import * as SecureStore from "expo-secure-store";
2
import {
3
  User,
4
  AuthResponse,
5
  VerifyTokenResponse,
6
  SignUpPayload,
7
  ResetPasswordRequestResponse,
8
  ResetPasswordResponse,
9
  UserUpdatePayload,
10
  UserUpdateResponse,
11
  MoodComponentPayload,
12
  MoodEntryPayload,
13
  SleepRecordPayload,
14
  CreateTriggerPayload,
15
  PaginatedResponse,
16
} from "@/lib/api/types";
17

18
const API_BASE_URL = process.env.EXPO_PUBLIC_API_BASE_URL;
19
const TOKEN_KEY = process.env.EXPO_PUBLIC_TOKEN_KEY;
20

21
class ApiClient {
22
  private async getStoredToken(): Promise<string | null> {
23
    try {
24
      return await SecureStore.getItemAsync(TOKEN_KEY);
25
    } catch (error) {
26
      console.error("Failed to get stored token:", error);
27
      return null;
28
    }
29
  }
30

31
  private async storeToken(token: string): Promise<void> {
32
    try {
33
      await SecureStore.setItemAsync(TOKEN_KEY, token);
34
    } catch (error) {
35
      console.error("Failed to store token:", error);
36
      throw error;
37
    }
38
  }
39

40
  private async removeToken(): Promise<void> {
41
    try {
42
      await SecureStore.deleteItemAsync(TOKEN_KEY);
43
    } catch (error) {
44
      console.error("Failed to remove token:", error);
45
    }
46
  }
47

48
  async getStoredAuthData(): Promise<{ token: string | null }> {
49
    const token = await this.getStoredToken();
50
    return { token };
51
  }
52

53
  private async request<T>(
54
    endpoint: string,
55
    options: RequestInit = {},
56
  ): Promise<T> {
57
    const token = await this.getStoredToken();
58
    const isFormData = options.body instanceof FormData;
59

60
    const config: RequestInit = {
61
      ...options,
62
      headers: {
63
        ...(token && { Authorization: `Bearer ${token}` }),
64
        ...(!isFormData && { "Content-Type": "application/json" }),
65
        ...options.headers,
66
      },
67
    };
68

69
    const response = await fetch(`${API_BASE_URL}${endpoint}`, config);
70

71
    if (!response.ok) {
72
      const error = await response
73
        .json()
74
        .catch(() => ({ error: "Network error" }));
75

76
      console.error("[API]: ", error, response.status);
77

78
      throw new Error(error.error || `HTTP ${response.status}`);
79
    }
80

81
    return response.json();
82
  }
83

84
  // Auth
85
  async login(
86
    email: string,
87
    password: string,
88
  ): Promise<AuthResponse | { isActive: boolean }> {
89
    const response = await this.request<AuthResponse>("/auth/login", {
90
      method: "POST",
91
      body: JSON.stringify({ email, password }),
92
    });
93

94
    if (response.token) {
95
      await this.storeToken(response.token);
96
    }
97

98
    return response;
99
  }
100

101
  async activate(code: number): Promise<ActivateResponse> {
102
    return this.request("/auth/activate", {
103
      method: "POST",
104
      body: JSON.stringify({ code }),
105
    });
106
  }
107

108
  async requestActivateCode(): Promise<void> {
109
    return this.request("/auth/resend_code", {
110
      method: "POST",
111
    });
112
  }
113

114
  async signup(user: SignUpPayload): Promise<AuthResponse> {
115
    const response = await this.request<AuthResponse>("/users", {
116
      method: "POST",
117
      body: JSON.stringify({ user }),
118
    });
119

120
    await this.storeToken(response.token);
121
    return response;
122
  }
123

124
  async forgotPassword(email: string): Promise<ResetPasswordRequestResponse> {
125
    return this.request("/auth/forgot-password", {
126
      method: "POST",
127
      body: JSON.stringify({ email }),
128
    });
129
  }
130

131
  async resetPassword(
132
    token: string,
133
    password: string,
134
  ): Promise<ResetPasswordResponse> {
135
    return this.request("/auth/reset-password", {
136
      method: "POST",
137
      body: JSON.stringify({ token, newPassword: password }),
138
    });
139
  }
140

141
  async verifyToken(): Promise<VerifyTokenResponse> {
142
    try {
143
      return await this.request("/auth/verify");
144
    } catch (error) {
145
      // Token is invalid or expired
146
      await this.removeToken();
147
      throw error;
148
    }
149
  }
150

151
  async logout(): Promise<void> {
152
    await this.removeToken();
153
  }
154

155
  // User-related
156
  async updateUser(user: UserUpdatePayload): Promise<User> {
157
    return await this.request(`/users/${user.id}`, {
158
      method: "PUT",
159
      body: JSON.stringify({ user }),
160
    });
161
  }
162

163
  async avatar(formData: FormData): Promise<User> {
164
    return await this.request(`/users/me/avatar`, {
165
      method: "POST",
166
      body: formData,
167
      headers: {
168
        "Content-Type": "multipart/form-data",
169
      } as any,
170
    });
171
  }
172

173
  async removeAvatar(): Promise<void> {
174
    return await this.request(`/users/me/avatar`, {
175
      method: "DELETE",
176
    });
177
  }
178

179
  // Mood-related
180
  async createMoodEntry(
181
    mood: CreateMoodEntryPayload,
182
  ): Promise<MoodEntryResponse> {
183
    return await this.request(`/moods`, {
184
      method: "POST",
185
      body: JSON.stringify({ mood }),
186
    });
187
  }
188

189
  async getMoodEntries(params?: {
190
    from?: string;
191
    to?: string;
192
    page?: number;
193
    limit?: number;
194
  }): Promise<PaginatedResponse<MoodEntry>> {
195
    const query = new URLSearchParams();
196
    if (params?.from) query.set("from", params.from);
197
    if (params?.to) query.set("to", params.to);
198
    if (params?.page) query.set("page", String(params.page));
199
    if (params?.limit) query.set("limit", String(params.limit));
200

201
    const qs = query.toString();
202
    return this.request(`/moods${qs ? `?${qs}` : ""}`);
203
  }
204

205
  async getMoodEntry(id: string): Promise<MoodEntry> {
206
    return this.request(`/moods/${id}`);
207
  }
208

209
  async deleteMoodEntry(id: string): Promise<void> {
210
    return this.request(`/moods/${id}`, { method: "DELETE" });
211
  }
212

213
  // Sleep Record
214
  async createSleepRecord(
215
    sleepRecord: CreateSleepRecordPayload,
216
  ): Promise<SleepRecordResponse> {
217
    return await this.request(`/sleep_records`, {
218
      method: "POST",
219
      body: JSON.stringify({ sleepRecord }),
220
    });
221
  }
222

223
  async getSleepRecords(params?: {
224
    from?: string;
225
    to?: string;
226
    page?: number;
227
    limit?: number;
228
  }): Promise<PaginatedResponse<SleepRecord>> {
229
    const query = new URLSearchParams();
230
    if (params?.from) query.set("from", params.from);
231
    if (params?.to) query.set("to", params.to);
232
    if (params?.page) query.set("page", String(params.page));
233
    if (params?.limit) query.set("limit", String(params.limit));
234

235
    const qs = query.toString();
236
    return this.request(`/sleep_records${qs ? `?${qs}` : ""}`);
237
  }
238

239
  // Trigger
240
  async createTrigger(trigger: CreateTriggerPayload): Promise<TriggerResponse> {
241
    return await this.request(`/triggers`, {
242
      method: "POST",
243
      body: JSON.stringify({ trigger }),
244
    });
245
  }
246

247
  async getTriggers(params?: {
248
    from?: string;
249
    to?: string;
250
    page?: number;
251
    limit?: number;
252
  }): Promise<PaginatedResponse<Trigger>> {
253
    const query = new URLSearchParams();
254
    if (params?.from) query.set("from", params.from);
255
    if (params?.to) query.set("to", params.to);
256
    if (params?.page) query.set("page", String(params.page));
257
    if (params?.limit) query.set("limit", String(params.limit));
258

259
    const qs = query.toString();
260
    return this.request(`/triggers${qs ? `?${qs}` : ""}`);
261
  }
262

263
  async getTrigger(id: string): Promise<Trigger> {
264
    return this.request(`/triggers/${id}`);
265
  }
266

267
  async deleteTrigger(id: string): Promise<void> {
268
    return this.request(`/triggers/${id}`, { method: "DELETE" });
269
  }
270
}
271

272
export const apiClient = new ApiClient();