285 lines
9.1 KiB
TypeScript
285 lines
9.1 KiB
TypeScript
import { Injectable } from '@angular/core';
|
|
import { HttpClient } from '@angular/common/http';
|
|
import { BehaviorSubject, Observable, of } from 'rxjs';
|
|
import { catchError, map, tap } from 'rxjs/operators';
|
|
import { User } from '../models/user.model';
|
|
import { environment } from '../../environments/environment';
|
|
import { StorageService } from './storage.service';
|
|
|
|
// Clave para almacenar información de sesión
|
|
const AUTH_TOKEN_KEY = 'auth_token';
|
|
const USER_KEY = 'user_data';
|
|
|
|
// Interfaz para credenciales de login
|
|
interface LoginCredentials {
|
|
email: string;
|
|
password: string;
|
|
}
|
|
|
|
// Interfaz para datos de registro
|
|
interface RegisterData {
|
|
name: string;
|
|
email: string;
|
|
password: string;
|
|
}
|
|
|
|
@Injectable({
|
|
providedIn: 'root'
|
|
})
|
|
export class AuthService {
|
|
private apiUrl = `${environment.apiUrl}/api/users`;
|
|
private authUrl = `${environment.apiUrl}/api/auth`;
|
|
private currentUserSubject = new BehaviorSubject<User | null>(null);
|
|
public currentUser$ = this.currentUserSubject.asObservable();
|
|
private isInitialized = false;
|
|
|
|
constructor(
|
|
private http: HttpClient,
|
|
private storageService: StorageService
|
|
) {
|
|
// Intentar recuperar sesión anterior
|
|
this.initializeSession();
|
|
}
|
|
|
|
/**
|
|
* Inicializa la sesión si hay datos guardados
|
|
*/
|
|
async initializeSession() {
|
|
if (this.isInitialized) return;
|
|
|
|
try {
|
|
const userData = await this.storageService.get(USER_KEY) as User | null;
|
|
|
|
if (userData) {
|
|
console.log('Sesión recuperada del almacenamiento local:', userData);
|
|
// Normalizar campos para compatibilidad
|
|
this.normalizeUserData(userData);
|
|
this.currentUserSubject.next(userData);
|
|
} else {
|
|
console.log('No hay sesión guardada, redirigiendo a login');
|
|
this.currentUserSubject.next(null);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error al inicializar sesión:', error);
|
|
this.currentUserSubject.next(null);
|
|
}
|
|
|
|
this.isInitialized = true;
|
|
}
|
|
|
|
/**
|
|
* Espera a que se complete la inicialización
|
|
* Importante para evitar redirecciones erróneas cuando la app inicia
|
|
*/
|
|
async waitForInitialization(): Promise<void> {
|
|
if (this.isInitialized) return;
|
|
|
|
// Si no se ha inicializado, iniciamos el proceso
|
|
await this.initializeSession();
|
|
|
|
// Esperar hasta que se inicialice (con timeout)
|
|
let attempts = 0;
|
|
while (!this.isInitialized && attempts < 10) {
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
attempts++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Normaliza los datos de usuario para compatibilidad entre frontend/backend
|
|
*/
|
|
private normalizeUserData(user: User) {
|
|
// Sincronizar campos de imagen
|
|
if (user.profilePicUrl && !user.profilePic) {
|
|
user.profilePic = user.profilePicUrl;
|
|
} else if (user.profilePic && !user.profilePicUrl) {
|
|
user.profilePicUrl = user.profilePic;
|
|
}
|
|
|
|
// Sincronizar preferencias de notificaciones
|
|
if (user.notificationsEnabled !== undefined && !user.preferences) {
|
|
user.preferences = {
|
|
notifications: user.notificationsEnabled,
|
|
favoriteClasses: []
|
|
};
|
|
} else if (user.preferences?.notifications !== undefined && user.notificationsEnabled === undefined) {
|
|
user.notificationsEnabled = user.preferences.notifications;
|
|
}
|
|
}
|
|
|
|
|
|
getCurrentUser(): User | null {
|
|
return this.currentUserSubject.value;
|
|
}
|
|
|
|
/**
|
|
* Iniciar sesión con email y contraseña
|
|
*/
|
|
login(credentials: LoginCredentials): Observable<User> {
|
|
console.log('Intentando login con:', credentials);
|
|
return this.http.post<any>(`${this.authUrl}/login`, credentials).pipe(
|
|
tap(async (response) => {
|
|
console.log('Respuesta de login:', response);
|
|
|
|
// Guardar token si el backend lo devuelve
|
|
if (response.token) {
|
|
await this.storageService.set(AUTH_TOKEN_KEY, response.token);
|
|
}
|
|
|
|
// Verificar y normalizar datos del usuario
|
|
const user = response.user || response;
|
|
this.normalizeUserData(user);
|
|
|
|
// Guardar datos del usuario
|
|
await this.storageService.set(USER_KEY, user);
|
|
|
|
// Actualizar estado de sesión
|
|
this.currentUserSubject.next(user);
|
|
}),
|
|
map(response => response.user || response)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Registrar un nuevo usuario
|
|
*/
|
|
register(userData: RegisterData): Observable<User> {
|
|
console.log('Registrando usuario:', userData);
|
|
const registerData = {
|
|
name: userData.name,
|
|
email: userData.email,
|
|
password: userData.password
|
|
};
|
|
|
|
return this.http.post<any>(`${this.authUrl}/register`, registerData).pipe(
|
|
tap(async (response) => {
|
|
console.log('Respuesta de registro:', response);
|
|
|
|
// Guardar token si el backend lo devuelve
|
|
if (response.token) {
|
|
await this.storageService.set(AUTH_TOKEN_KEY, response.token);
|
|
}
|
|
|
|
// Verificar y normalizar datos del usuario
|
|
const user = response.user || response;
|
|
this.normalizeUserData(user);
|
|
|
|
// Guardar datos del usuario
|
|
await this.storageService.set(USER_KEY, user);
|
|
|
|
// Actualizar estado de sesión
|
|
this.currentUserSubject.next(user);
|
|
}),
|
|
map(response => response.user || response)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Método para simular login/registro en una app de demo
|
|
*/
|
|
simulateAuth(userData: User): Observable<User> {
|
|
return of(userData).pipe(
|
|
tap(async (user) => {
|
|
this.normalizeUserData(user);
|
|
await this.storageService.set(USER_KEY, user);
|
|
this.currentUserSubject.next(user);
|
|
})
|
|
);
|
|
}
|
|
|
|
updateUserProfile(userData: Partial<User>): Observable<User> {
|
|
const currentUser = this.getCurrentUser();
|
|
if (!currentUser) {
|
|
return of(currentUser as unknown as User);
|
|
}
|
|
|
|
console.log('Actualizando perfil de usuario con:', userData);
|
|
|
|
// Asegurar que ambos campos de imagen estén sincronizados
|
|
if (userData.profilePic && !userData.profilePicUrl) {
|
|
userData.profilePicUrl = userData.profilePic;
|
|
} else if (userData.profilePicUrl && !userData.profilePic) {
|
|
userData.profilePic = userData.profilePicUrl;
|
|
}
|
|
|
|
// Sincronizar notificaciones entre los dos formatos
|
|
if (userData.preferences?.notifications !== undefined && userData.notificationsEnabled === undefined) {
|
|
userData.notificationsEnabled = userData.preferences.notifications;
|
|
} else if (userData.notificationsEnabled !== undefined &&
|
|
(!userData.preferences || userData.preferences.notifications === undefined)) {
|
|
if (!userData.preferences) userData.preferences = { notifications: false, favoriteClasses: [] };
|
|
userData.preferences.notifications = userData.notificationsEnabled;
|
|
}
|
|
|
|
// Solo enviamos al servidor los campos que espera
|
|
const backendUserData = {
|
|
name: userData.name,
|
|
profilePicUrl: userData.profilePicUrl,
|
|
notificationsEnabled: userData.notificationsEnabled ||
|
|
(userData.preferences ? userData.preferences.notifications : undefined)
|
|
};
|
|
|
|
return this.http.put<User>(`${this.apiUrl}/${currentUser.id}`, backendUserData).pipe(
|
|
tap(async (user) => {
|
|
console.log('Usuario actualizado correctamente:', user);
|
|
|
|
// Sincronizar campos para mantener compatibilidad
|
|
if (user.profilePicUrl && !user.profilePic) {
|
|
user.profilePic = user.profilePicUrl;
|
|
} else if (user.profilePic && !user.profilePicUrl) {
|
|
user.profilePicUrl = user.profilePic;
|
|
}
|
|
|
|
// Mantener los campos que espera el frontend
|
|
if (user.notificationsEnabled !== undefined && !user.preferences) {
|
|
user.preferences = {
|
|
notifications: user.notificationsEnabled,
|
|
favoriteClasses: currentUser.preferences?.favoriteClasses || []
|
|
};
|
|
}
|
|
|
|
// Actualizar almacenamiento local
|
|
await this.storageService.set(USER_KEY, user);
|
|
|
|
this.currentUserSubject.next(user);
|
|
}),
|
|
catchError(error => {
|
|
console.error('Error al actualizar usuario:', error);
|
|
// Devolver el usuario actualizado localmente para que la UI no se rompa
|
|
// en caso de error de red
|
|
const updatedUser = { ...currentUser, ...userData };
|
|
this.currentUserSubject.next(updatedUser);
|
|
return of(updatedUser);
|
|
})
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Cerrar sesión y eliminar datos
|
|
*/
|
|
async logout(): Promise<boolean> {
|
|
try {
|
|
// En una aplicación real, enviaríamos petición al backend
|
|
// this.http.post(`${this.authUrl}/logout`, {}).subscribe();
|
|
|
|
// Limpiar datos de sesión
|
|
await this.storageService.clear(); // Limpiamos TODA la caché de preferencias
|
|
|
|
// Actualizar estado
|
|
this.currentUserSubject.next(null);
|
|
this.isInitialized = false; // Permitir nueva inicialización
|
|
|
|
return true;
|
|
} catch (error) {
|
|
console.error('Error al cerrar sesión:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verificar si hay sesión activa
|
|
*/
|
|
isLoggedIn(): boolean {
|
|
return !!this.currentUserSubject.value;
|
|
}
|
|
} |