frontend/lib: reorganizing api calls before introducing query cache
Which means: - Splitting types into another file - Organizing internal code between what's local and what's not - Adding a barrel file as well Might evaluate to split even further, into different groups of files, and combine it later, so for example have all mood-related calls in a single place, users, auth, tokens, etc
Pedro Lucas Porcellis porcellis@eletrotupi.com · 5 months ago
d706da942de64ad47412e67cb183d34e5e1fd05e Browse filesParents
17dd47aChanged files
3
Additions
+128
Deletions
-51
frontend/lib/api.ts → frontend/lib/api/client.ts
1 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';
2 15
3 16
const API_BASE_URL = process.env.EXPO_PUBLIC_API_BASE_URL;
4 17
const TOKEN_KEY = process.env.EXPO_PUBLIC_TOKEN_KEY; // Gary B.B Coleman fucking slaps
5 18
6 -
export interface User {
7 -
id: number;
8 -
email: string;
9 -
firstName: string;
10 -
lastName?: string;
11 -
updatedAt: Date;
12 -
avatarKey?: string;
13 -
avatarURL?: string;
14 -
}
15 -
16 -
export interface AuthResponse {
17 -
token: string;
18 -
user: User;
19 -
}
20 -
21 -
export interface UserUpdateResponse {
22 -
user: User
23 -
}
24 -
25 19
class ApiClient {
26 20
private async getStoredToken(): Promise<string | null> {
27 21
try {
28 22
return await SecureStore.getItemAsync(TOKEN_KEY);
29 23
} catch (error) {
47 41
} catch (error) {
48 42
console.error('Failed to remove token:', error);
49 43
}
50 44
}
51 45
46 +
async getStoredAuthData(): Promise<{ token: string | null }> {
47 +
const token = await this.getStoredToken();
48 +
return { token };
49 +
}
50 +
52 51
private async request<T>(
53 52
endpoint: string,
54 53
options: RequestInit = {}
55 54
): Promise<T> {
56 55
const token = await this.getStoredToken();
77 76
}
78 77
79 78
return response.json();
80 79
}
81 80
82 -
async avatar(formData: FormData): Promise<void> {
83 -
return await this.request(`/users/me/avatar`, {
84 -
method: 'POST',
85 -
body: formData,
86 -
headers: {
87 -
'Content-Type': 'multipart/form-data',
88 -
} as any,
89 -
})
90 -
}
91 -
92 -
async removeAvatar(): Promise<void> {
93 -
return await this.request(`/users/me/avatar`, {
94 -
method: 'DELETE'
95 -
});
96 -
}
97 -
81 +
// Auth
98 82
async login(email: string, password: string): Promise<AuthResponse> {
99 83
const response = await this.request<AuthResponse>('/auth/login', {
100 84
method: 'POST',
101 85
body: JSON.stringify({ email, password }),
102 86
});
103 87
104 88
await this.storeToken(response.token);
105 89
return response;
106 90
}
107 91
108 -
async signup(userData: {
109 -
first_name: string;
110 -
last_name?: string;
111 -
email: string;
112 -
password: string;
113 -
}): Promise<AuthResponse> {
92 +
async signup(data: SignUpPayload): Promise<AuthResponse> {
114 93
const response = await this.request<AuthResponse>('/users', {
115 94
method: 'POST',
116 -
body: JSON.stringify(userData),
95 +
body: JSON.stringify(data),
117 96
});
118 97
119 98
await this.storeToken(response.token);
120 99
return response;
121 100
}
122 101
123 -
async forgotPassword(email: string): Promise<{ message: string; token?: string }> {
102 +
async forgotPassword(email: string): Promise<ResetPasswordRequestResponse> {
124 103
return this.request('/auth/forgot-password', {
125 104
method: 'POST',
126 105
body: JSON.stringify({ email }),
127 106
});
128 107
}
129 108
130 -
async resetPassword(token: string, password: string): Promise<{ message: string }> {
109 +
async resetPassword(token: string, password: string): Promise<ResetPasswordResponse> {
131 110
return this.request('/auth/reset-password', {
132 111
method: 'POST',
133 112
body: JSON.stringify({ token, newPassword: password }),
134 113
});
135 114
}
136 115
137 -
async verifyToken(): Promise<{ valid: boolean; userId?: number; email?: string }> {
116 +
async verifyToken(): Promise<VerifyTokenResponse> {
138 117
try {
139 118
return await this.request('/auth/verify');
140 119
} catch (error) {
141 120
// Token is invalid or expired
142 121
await this.removeToken();
146 125
147 126
async logout(): Promise<void> {
148 127
await this.removeToken();
149 128
}
150 129
151 -
async getStoredAuthData(): Promise<{ token: string | null }> {
152 -
const token = await this.getStoredToken();
153 -
return { token };
154 -
}
155 -
156 -
async updateUser(user): Promise<void> {
130 +
// User-related
131 +
async updateUser(user: UserUpdatePayload): Promise<User> {
157 132
return await this.request(`/users/${user.id}`, {
158 133
method: 'PUT',
159 134
body: JSON.stringify({ user })
160 135
})
161 136
}
162 137
138 +
async avatar(formData: FormData): Promise<void> {
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
163 155
async createMoodEntry(moodEntry): Promise<void> {
164 156
return await this.request(`/moods`, {
165 157
method: 'POST',
166 158
body: JSON.stringify({ mood: moodEntry })
167 159
})
168 160
}
169 161
}
170 162
171 163
export const apiClient = new ApiClient();
/dev/null → frontend/lib/api/index.ts
1 +
export {
2 +
apiClient
3 +
} from '@/lib/api/client';
4 +
5 +
export type {
6 +
User,
7 +
AuthResponse,
8 +
MoodEntryPayload,
9 +
MoodComponentPayload,
10 +
PaginatedResponse,
11 +
UserUpdateResponse
12 +
} from '@/lib/api/types';
/dev/null → frontend/lib/api/types.ts
1 +
export interface User {
2 +
id: number;
3 +
email: string;
4 +
firstName: string;
5 +
lastName?: string;
6 +
updatedAt: Date;
7 +
avatarKey?: string;
8 +
avatarURL?: string;
9 +
};
10 +
11 +
// Auth
12 +
export interface AuthResponse {
13 +
token: string;
14 +
user: User;
15 +
};
16 +
17 +
export interface VerifyTokenResponse {
18 +
valid: boolean;
19 +
userId?: number;
20 +
email?: string;
21 +
user: Pick<User, 'firstName' | 'lastName'>;
22 +
};
23 +
24 +
export interface SignUpPayload {
25 +
firstName: string;
26 +
lastName?: string;
27 +
email: string;
28 +
password: string;
29 +
};
30 +
31 +
export interface ResetPasswordRequestResponse {
32 +
message: string;
33 +
token?: string;
34 +
};
35 +
36 +
export interface ResetPasswordResponse {
37 +
message: string;
38 +
};
39 +
40 +
// User
41 +
export interface UserUpdatePayload extends Partial<User> {
42 +
id: number;
43 +
}
44 +
45 +
export interface UserUpdateResponse {
46 +
user: User
47 +
};
48 +
49 +
// Mood entries
50 +
export interface MoodComponentPayload {
51 +
component: string;
52 +
intensity: string;
53 +
};
54 +
55 +
56 +
export interface MoodEntryPayload {
57 +
annotation: string;
58 +
moment: Date;
59 +
selectedMood: string;
60 +
anxietyLevel: number;
61 +
energyLevel: number;
62 +
stressLevel: number;
63 +
moodComponents: MoodComponentPayload[]
64 +
};
65 +
66 +
67 +
// Generic stuff
68 +
export interface PaginatedResponse<T> {
69 +
entries: T[];
70 +
total: number;
71 +
page: number;
72 +
nextPage: number | null;
73 +
};