eletrotupi / tcc/ frontend/lib/api/client.ts4049ccf31aa524735a52c61d0bece7515197e8c8
5.6 KB | 212 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> {
84
    const response = await this.request<AuthResponse>('/auth/login', {
85
      method: 'POST',
86
      body: JSON.stringify({ email, password }),
87
    });
88

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

93
  async signup(user: SignUpPayload): Promise<AuthResponse> {
94
    const response = await this.request<AuthResponse>('/users', {
95
      method: 'POST',
96
      body: JSON.stringify({ user }),
97
    });
98

99
    await this.storeToken(response.token);
100
    return response;
101
  }
102

103
  async forgotPassword(email: string): Promise<ResetPasswordRequestResponse> {
104
    return this.request('/auth/forgot-password', {
105
      method: 'POST',
106
      body: JSON.stringify({ email }),
107
    });
108
  }
109

110
  async resetPassword(token: string, password: string): Promise<ResetPasswordResponse> {
111
    return this.request('/auth/reset-password', {
112
      method: 'POST',
113
      body: JSON.stringify({ token, newPassword: password }),
114
    });
115
  }
116

117
  async verifyToken(): Promise<VerifyTokenResponse> {
118
    try {
119
      return await this.request('/auth/verify');
120
    } catch (error) {
121
      // Token is invalid or expired
122
      await this.removeToken();
123
      throw error;
124
    }
125
  }
126

127
  async logout(): Promise<void> {
128
    await this.removeToken();
129
  }
130

131
  // User-related
132
  async updateUser(user: UserUpdatePayload): Promise<User> {
133
    return await this.request(`/users/${user.id}`, {
134
      method: 'PUT',
135
      body: JSON.stringify({ user })
136
    })
137
  }
138

139
  async avatar(formData: FormData): Promise<User> {
140
    return await this.request(`/users/me/avatar`, {
141
      method: 'POST',
142
      body: formData,
143
      headers: {
144
        'Content-Type': 'multipart/form-data',
145
      } as any,
146
    })
147
  }
148

149
  async removeAvatar(): Promise<void> {
150
    return await this.request(`/users/me/avatar`, {
151
      method: 'DELETE'
152
    });
153
  }
154

155
  // Mood-related
156
  async createMoodEntry(mood: CreateMoodEntryPayload): Promise<MoodEntryResponse> {
157
    return await this.request(`/moods`, {
158
      method: 'POST',
159
      body: JSON.stringify({ mood })
160
    })
161
  }
162

163
  async getMoodEntries(params?: {
164
    from?: string;
165
    to?: string;
166
    page?: number;
167
    limit?: number;
168
  }): Promise<PaginatedResponse<MoodEntry>> {
169
    const query = new URLSearchParams();
170
    if (params?.from)  query.set('from', params.from);
171
    if (params?.to)    query.set('to', params.to);
172
    if (params?.page)  query.set('page', String(params.page));
173
    if (params?.limit) query.set('limit', String(params.limit));
174

175
    const qs = query.toString();
176
    return this.request(`/moods${qs ? `?${qs}` : ''}`);
177
  }
178

179
  async getMoodEntry(id: string): Promise<MoodEntry> {
180
    return this.request(`/moods/${id}`);
181
  }
182

183
  async deleteMoodEntry(id: string): Promise<void> {
184
    return this.request(`/moods/${id}`, { method: 'DELETE' });
185
  }
186

187
  // Sleep Record
188
  async createSleepRecord(sleepRecord: CreateSleepRecordPayload): Promise<SleepRecordResponse> {
189
    return await this.request(`/sleep_records`, {
190
      method: 'POST',
191
      body: JSON.stringify({ sleepRecord })
192
    })
193
  }
194

195
  async getSleepRecords(params?: {
196
    from?: string;
197
    to?: string;
198
    page?: number;
199
    limit?: number;
200
  }): Promise<PaginatedResponse<SleepRecord>> {
201
    const query = new URLSearchParams();
202
    if (params?.from)  query.set('from', params.from);
203
    if (params?.to)    query.set('to', params.to);
204
    if (params?.page)  query.set('page', String(params.page));
205
    if (params?.limit) query.set('limit', String(params.limit));
206

207
    const qs = query.toString();
208
    return this.request(`/sleep_records${qs ? `?${qs}` : ''}`);
209
  }
210
}
211

212
export const apiClient = new ApiClient();