eletrotupi / tcc/ frontend/lib/api/client.ts6d8c0a74220b36e86fd53538617fd70ce2618f59
5.8 KB | 222 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 signup(user: SignUpPayload): Promise<AuthResponse> {
104
    const response = await this.request<AuthResponse>('/users', {
105
      method: 'POST',
106
      body: JSON.stringify({ user }),
107
    });
108

109
    await this.storeToken(response.token);
110
    return response;
111
  }
112

113
  async forgotPassword(email: string): Promise<ResetPasswordRequestResponse> {
114
    return this.request('/auth/forgot-password', {
115
      method: 'POST',
116
      body: JSON.stringify({ email }),
117
    });
118
  }
119

120
  async resetPassword(token: string, password: string): Promise<ResetPasswordResponse> {
121
    return this.request('/auth/reset-password', {
122
      method: 'POST',
123
      body: JSON.stringify({ token, newPassword: password }),
124
    });
125
  }
126

127
  async verifyToken(): Promise<VerifyTokenResponse> {
128
    try {
129
      return await this.request('/auth/verify');
130
    } catch (error) {
131
      // Token is invalid or expired
132
      await this.removeToken();
133
      throw error;
134
    }
135
  }
136

137
  async logout(): Promise<void> {
138
    await this.removeToken();
139
  }
140

141
  // User-related
142
  async updateUser(user: UserUpdatePayload): Promise<User> {
143
    return await this.request(`/users/${user.id}`, {
144
      method: 'PUT',
145
      body: JSON.stringify({ user })
146
    })
147
  }
148

149
  async avatar(formData: FormData): Promise<User> {
150
    return await this.request(`/users/me/avatar`, {
151
      method: 'POST',
152
      body: formData,
153
      headers: {
154
        'Content-Type': 'multipart/form-data',
155
      } as any,
156
    })
157
  }
158

159
  async removeAvatar(): Promise<void> {
160
    return await this.request(`/users/me/avatar`, {
161
      method: 'DELETE'
162
    });
163
  }
164

165
  // Mood-related
166
  async createMoodEntry(mood: CreateMoodEntryPayload): Promise<MoodEntryResponse> {
167
    return await this.request(`/moods`, {
168
      method: 'POST',
169
      body: JSON.stringify({ mood })
170
    })
171
  }
172

173
  async getMoodEntries(params?: {
174
    from?: string;
175
    to?: string;
176
    page?: number;
177
    limit?: number;
178
  }): Promise<PaginatedResponse<MoodEntry>> {
179
    const query = new URLSearchParams();
180
    if (params?.from)  query.set('from', params.from);
181
    if (params?.to)    query.set('to', params.to);
182
    if (params?.page)  query.set('page', String(params.page));
183
    if (params?.limit) query.set('limit', String(params.limit));
184

185
    const qs = query.toString();
186
    return this.request(`/moods${qs ? `?${qs}` : ''}`);
187
  }
188

189
  async getMoodEntry(id: string): Promise<MoodEntry> {
190
    return this.request(`/moods/${id}`);
191
  }
192

193
  async deleteMoodEntry(id: string): Promise<void> {
194
    return this.request(`/moods/${id}`, { method: 'DELETE' });
195
  }
196

197
  // Sleep Record
198
  async createSleepRecord(sleepRecord: CreateSleepRecordPayload): Promise<SleepRecordResponse> {
199
    return await this.request(`/sleep_records`, {
200
      method: 'POST',
201
      body: JSON.stringify({ sleepRecord })
202
    })
203
  }
204

205
  async getSleepRecords(params?: {
206
    from?: string;
207
    to?: string;
208
    page?: number;
209
    limit?: number;
210
  }): Promise<PaginatedResponse<SleepRecord>> {
211
    const query = new URLSearchParams();
212
    if (params?.from)  query.set('from', params.from);
213
    if (params?.to)    query.set('to', params.to);
214
    if (params?.page)  query.set('page', String(params.page));
215
    if (params?.limit) query.set('limit', String(params.limit));
216

217
    const qs = query.toString();
218
    return this.request(`/sleep_records${qs ? `?${qs}` : ''}`);
219
  }
220
}
221

222
export const apiClient = new ApiClient();