eletrotupi / tcc/ frontend/lib/api/client.tsda58efba46e647a5b167032232fc6bd69007195d
6.0 KB | 228 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
  PaginatedResponse
15
} from '@/lib/api/types';
16

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

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

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

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

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

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

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

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

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

74
      console.error("[API]: ", error, response.status)
75

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

79
    return response.json();
80
  }
81

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

89
    if (response.token) {
90
      await this.storeToken(response.token);
91
    }
92

93
    return response;
94
  }
95

96
  async activate(code: number): Promise<ActivateResponse> {
97
    return this.request('/auth/activate', {
98
      method: 'POST',
99
      body: JSON.stringify({ code }),
100
    });
101
  }
102

103
  async requestActivateCode(): Promise<void> {
104
    return this.request('/auth/resend_code', {
105
      method: 'POST'
106
    })
107
  }
108

109
  async signup(user: SignUpPayload): Promise<AuthResponse> {
110
    const response = await this.request<AuthResponse>('/users', {
111
      method: 'POST',
112
      body: JSON.stringify({ user }),
113
    });
114

115
    await this.storeToken(response.token);
116
    return response;
117
  }
118

119
  async forgotPassword(email: string): Promise<ResetPasswordRequestResponse> {
120
    return this.request('/auth/forgot-password', {
121
      method: 'POST',
122
      body: JSON.stringify({ email }),
123
    });
124
  }
125

126
  async resetPassword(token: string, password: string): Promise<ResetPasswordResponse> {
127
    return this.request('/auth/reset-password', {
128
      method: 'POST',
129
      body: JSON.stringify({ token, newPassword: password }),
130
    });
131
  }
132

133
  async verifyToken(): Promise<VerifyTokenResponse> {
134
    try {
135
      return await this.request('/auth/verify');
136
    } catch (error) {
137
      // Token is invalid or expired
138
      await this.removeToken();
139
      throw error;
140
    }
141
  }
142

143
  async logout(): Promise<void> {
144
    await this.removeToken();
145
  }
146

147
  // User-related
148
  async updateUser(user: UserUpdatePayload): Promise<User> {
149
    return await this.request(`/users/${user.id}`, {
150
      method: 'PUT',
151
      body: JSON.stringify({ user })
152
    })
153
  }
154

155
  async avatar(formData: FormData): Promise<User> {
156
    return await this.request(`/users/me/avatar`, {
157
      method: 'POST',
158
      body: formData,
159
      headers: {
160
        'Content-Type': 'multipart/form-data',
161
      } as any,
162
    })
163
  }
164

165
  async removeAvatar(): Promise<void> {
166
    return await this.request(`/users/me/avatar`, {
167
      method: 'DELETE'
168
    });
169
  }
170

171
  // Mood-related
172
  async createMoodEntry(mood: CreateMoodEntryPayload): Promise<MoodEntryResponse> {
173
    return await this.request(`/moods`, {
174
      method: 'POST',
175
      body: JSON.stringify({ mood })
176
    })
177
  }
178

179
  async getMoodEntries(params?: {
180
    from?: string;
181
    to?: string;
182
    page?: number;
183
    limit?: number;
184
  }): Promise<PaginatedResponse<MoodEntry>> {
185
    const query = new URLSearchParams();
186
    if (params?.from)  query.set('from', params.from);
187
    if (params?.to)    query.set('to', params.to);
188
    if (params?.page)  query.set('page', String(params.page));
189
    if (params?.limit) query.set('limit', String(params.limit));
190

191
    const qs = query.toString();
192
    return this.request(`/moods${qs ? `?${qs}` : ''}`);
193
  }
194

195
  async getMoodEntry(id: string): Promise<MoodEntry> {
196
    return this.request(`/moods/${id}`);
197
  }
198

199
  async deleteMoodEntry(id: string): Promise<void> {
200
    return this.request(`/moods/${id}`, { method: 'DELETE' });
201
  }
202

203
  // Sleep Record
204
  async createSleepRecord(sleepRecord: CreateSleepRecordPayload): Promise<SleepRecordResponse> {
205
    return await this.request(`/sleep_records`, {
206
      method: 'POST',
207
      body: JSON.stringify({ sleepRecord })
208
    })
209
  }
210

211
  async getSleepRecords(params?: {
212
    from?: string;
213
    to?: string;
214
    page?: number;
215
    limit?: number;
216
  }): Promise<PaginatedResponse<SleepRecord>> {
217
    const query = new URLSearchParams();
218
    if (params?.from)  query.set('from', params.from);
219
    if (params?.to)    query.set('to', params.to);
220
    if (params?.page)  query.set('page', String(params.page));
221
    if (params?.limit) query.set('limit', String(params.limit));
222

223
    const qs = query.toString();
224
    return this.request(`/sleep_records${qs ? `?${qs}` : ''}`);
225
  }
226
}
227

228
export const apiClient = new ApiClient();