feat: implement authentication flow and session management
- Update RootLayout to handle authentication state and redirect users based on their auth status. - Create an Index component to redirect unauthenticated users to the login page. - Modify ApiAuthRepository to fetch user session data using access and refresh tokens. - Introduce a new container file to manage use case instances for authentication and filament operations. - Enhance authStore to validate tokens and fetch user data from the API. - Add babel-plugin-module-resolver for improved module imports. - Update package.json scripts for running the app on Android and iOS. - Add expo-camera dependency for camera functionalities. - Update tsconfig.json to include infrastructure path mapping.
This commit is contained in:
@@ -9,17 +9,17 @@ import type { AuthSession, LoginInput, RegisterInput, GoogleOAuthInput } from '@
|
||||
export class ApiAuthRepository implements AuthRepository {
|
||||
async login(input: LoginInput): Promise<AuthSession> {
|
||||
const { data } = await httpClient.post('/auth/login', input);
|
||||
return this.mapSession(data);
|
||||
return this.fetchSession(data.access_token as string, data.refresh_token as string);
|
||||
}
|
||||
|
||||
async register(input: RegisterInput): Promise<AuthSession> {
|
||||
const { data } = await httpClient.post('/auth/register', input);
|
||||
return this.mapSession(data);
|
||||
return this.fetchSession(data.access_token as string, data.refresh_token as string);
|
||||
}
|
||||
|
||||
async loginWithGoogle(input: GoogleOAuthInput): Promise<AuthSession> {
|
||||
const { data } = await httpClient.post('/auth/oauth/google', input);
|
||||
return this.mapSession(data);
|
||||
return this.fetchSession(data.access_token as string, data.refresh_token as string);
|
||||
}
|
||||
|
||||
async refreshToken(refreshToken: string): Promise<Pick<AuthSession, 'accessToken' | 'refreshToken'>> {
|
||||
@@ -48,13 +48,15 @@ export class ApiAuthRepository implements AuthRepository {
|
||||
await httpClient.post('/auth/reset-password', { token, new_password: newPassword });
|
||||
}
|
||||
|
||||
private mapSession(data: Record<string, unknown>): AuthSession {
|
||||
const user = data.user as Record<string, unknown>;
|
||||
private async fetchSession(accessToken: string, refreshToken: string): Promise<AuthSession> {
|
||||
const { data: user } = await httpClient.get('/users/me', {
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
return {
|
||||
accessToken: data.access_token as string,
|
||||
refreshToken: data.refresh_token as string,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
user: {
|
||||
id: user.id as string,
|
||||
id: String(user.id),
|
||||
email: user.email as string,
|
||||
name: (user.name as string | null) ?? null,
|
||||
googleId: (user.google_id as string | null) ?? null,
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { ApiAuthRepository } from '@adapters/remote/ApiAuthRepository';
|
||||
import { ApiFilamentRepository } from '@adapters/remote/ApiFilamentRepository';
|
||||
import { ApiSpoolPresetRepository } from '@adapters/remote/ApiSpoolPresetRepository';
|
||||
import {
|
||||
LoginUseCase,
|
||||
RegisterUseCase,
|
||||
ForgotPasswordUseCase,
|
||||
ResetPasswordUseCase,
|
||||
LogoutUseCase,
|
||||
} from '@application/auth/AuthUseCases';
|
||||
import { CreateFilamentUseCase } from '@application/filament/CreateFilamentUseCase';
|
||||
import { UpdateFilamentUseCase } from '@application/filament/UpdateFilamentUseCase';
|
||||
import { ListFilamentsUseCase } from '@application/filament/ListFilamentsUseCase';
|
||||
import { DeleteFilamentUseCase } from '@application/filament/DeleteFilamentUseCase';
|
||||
import {
|
||||
ListPresetsUseCase,
|
||||
CreatePresetUseCase,
|
||||
UpdatePresetUseCase,
|
||||
DeletePresetUseCase,
|
||||
} from '@application/preset/PresetUseCases';
|
||||
|
||||
// Repositórios (singletons)
|
||||
const authRepository = new ApiAuthRepository();
|
||||
const filamentRepository = new ApiFilamentRepository();
|
||||
const presetRepository = new ApiSpoolPresetRepository();
|
||||
|
||||
// Use cases de autenticação
|
||||
export const loginUseCase = new LoginUseCase(authRepository);
|
||||
export const registerUseCase = new RegisterUseCase(authRepository);
|
||||
export const forgotPasswordUseCase = new ForgotPasswordUseCase(authRepository);
|
||||
export const resetPasswordUseCase = new ResetPasswordUseCase(authRepository);
|
||||
export const logoutUseCase = new LogoutUseCase(authRepository);
|
||||
|
||||
// Use cases de filamento
|
||||
export const createFilamentUseCase = new CreateFilamentUseCase(filamentRepository, presetRepository);
|
||||
export const updateFilamentUseCase = new UpdateFilamentUseCase(filamentRepository, presetRepository);
|
||||
export const listFilamentsUseCase = new ListFilamentsUseCase(filamentRepository);
|
||||
export const deleteFilamentUseCase = new DeleteFilamentUseCase(filamentRepository);
|
||||
|
||||
// Use cases de preset
|
||||
export const listPresetsUseCase = new ListPresetsUseCase(presetRepository);
|
||||
export const createPresetUseCase = new CreatePresetUseCase(presetRepository);
|
||||
export const updatePresetUseCase = new UpdatePresetUseCase(presetRepository);
|
||||
export const deletePresetUseCase = new DeletePresetUseCase(presetRepository);
|
||||
@@ -1,7 +1,8 @@
|
||||
import { create } from 'zustand';
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
import axios from 'axios';
|
||||
import type { User, AuthSession } from '@domain/User';
|
||||
import { SECURE_STORE_ACCESS_TOKEN, SECURE_STORE_REFRESH_TOKEN } from '@shared/constants';
|
||||
import { SECURE_STORE_ACCESS_TOKEN, SECURE_STORE_REFRESH_TOKEN, API_BASE_URL } from '@shared/constants';
|
||||
|
||||
interface AuthState {
|
||||
user: User | null;
|
||||
@@ -56,13 +57,26 @@ export const useAuthStore = create<AuthState>((set) => ({
|
||||
set({ isLoading: true });
|
||||
try {
|
||||
const accessToken = await SecureStore.getItemAsync(SECURE_STORE_ACCESS_TOKEN);
|
||||
const refreshToken = await SecureStore.getItemAsync(SECURE_STORE_REFRESH_TOKEN);
|
||||
if (accessToken) {
|
||||
// TODO: validar token com /api/v1/users/me e popular o user
|
||||
set({ accessToken, isAuthenticated: true, isLoading: false });
|
||||
const { data } = await axios.get(`${API_BASE_URL}/users/me`, {
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
const user: User = {
|
||||
id: String(data.id),
|
||||
email: data.email as string,
|
||||
name: (data.name as string | null) ?? null,
|
||||
googleId: (data.google_id as string | null) ?? null,
|
||||
createdAt: data.created_at as string,
|
||||
};
|
||||
set({ user, accessToken, refreshToken, isAuthenticated: true, isLoading: false });
|
||||
} else {
|
||||
set({ isLoading: false });
|
||||
}
|
||||
} catch {
|
||||
// Token inválido ou expirado — limpa a sessão
|
||||
await SecureStore.deleteItemAsync(SECURE_STORE_ACCESS_TOKEN);
|
||||
await SecureStore.deleteItemAsync(SECURE_STORE_REFRESH_TOKEN);
|
||||
set({ isLoading: false });
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user