eletrotupi / tcc/ commit / d706da9

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 files
Parents 17dd47a
Changed 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 +
};