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:
2026-03-14 13:22:17 -03:00
parent d7fb768d3b
commit 416e13893c
68 changed files with 1778 additions and 99 deletions
@@ -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,
+44
View File
@@ -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);
+17 -3
View File
@@ -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 });
}
},