eletrotupi / tcc/ frontend/lib/api/client.tsa8152cd7c2236ab5a4cb9335628c0878ecb8fce4
4.8 KB | 187 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
  PaginatedResponse
14
} from '@/lib/api/types';
15

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

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

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

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

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

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

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

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

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

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

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

78
    return response.json();
79
  }
80

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

187
export const apiClient = new ApiClient();