Adapted some config for the api

This commit is contained in:
Marta Borgia Leiva 2026-02-09 22:46:16 +01:00
parent 773fa7ad6d
commit d1e016b7df
Signed by: a-mayb3
GPG key ID: 293AAC4FED165CE3
9 changed files with 74 additions and 54 deletions

View file

@ -1,7 +1,11 @@
import { ApplicationConfig, provideBrowserGlobalErrorListeners, APP_INITIALIZER } from '@angular/core'; import {
ApplicationConfig,
provideBrowserGlobalErrorListeners,
APP_INITIALIZER,
} from '@angular/core';
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';
import { provideHttpClient, withInterceptors } from '@angular/common/http'; import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { catchError, of } from 'rxjs'; import { catchError, of, throwError } from 'rxjs';
import { routes } from './app.routes'; import { routes } from './app.routes';
import { httpInterceptor } from './interceptors/http.interceptor'; import { httpInterceptor } from './interceptors/http.interceptor';
@ -11,28 +15,30 @@ import { AuthService } from './services/auth.service';
* Initialize auth state on app startup by checking for existing session * Initialize auth state on app startup by checking for existing session
*/ */
function initializeAuth(authService: AuthService) { function initializeAuth(authService: AuthService) {
return () => authService.checkSession().pipe( return () =>
catchError((error) => { authService.checkSession().pipe(
// Session check failed - user is not logged in or session expired catchError((error) => {
console.log('No active session or session expired'); console.error('Session check failed:', error);
authService.clearAuthState(); if (error?.status === 401 || error?.status === 422) {
return of(null); authService.clearAuthState();
}) return of(null);
); }
authService.clearAuthState();
return throwError(() => error);
}),
);
} }
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [ providers: [
provideBrowserGlobalErrorListeners(), provideBrowserGlobalErrorListeners(),
provideRouter(routes), provideRouter(routes),
provideHttpClient( provideHttpClient(withInterceptors([httpInterceptor])),
withInterceptors([httpInterceptor])
),
{ {
provide: APP_INITIALIZER, provide: APP_INITIALIZER,
useFactory: initializeAuth, useFactory: initializeAuth,
deps: [AuthService], deps: [AuthService],
multi: true multi: true,
} },
] ],
}; };

View file

@ -1,8 +1,5 @@
<app-navbar /> <app-navbar />
<main class="main"></main> <main class="main">
<router-outlet />
</main>
<app-footer /> <app-footer />
<router-outlet />

View file

@ -4,15 +4,28 @@ import { LoginComponent } from './pages/login/login.component';
export const routes: Routes = [ export const routes: Routes = [
// Public routes // Public routes
{ {
path: 'login', path: 'login',
component: LoginComponent component: LoginComponent
}, },
// Protected routes - require authentication // Protected routes - require authentication
{ {
path: '', path: '',
canActivate: [authGuard], canActivate: [authGuard],
loadComponent: () => import('./pages/home/home.component').then(m => m.HomeComponent) children: [
{
path: '',
loadComponent: () => import('./pages/home/home.component').then(m => m.HomeComponent)
},
{
path: 'projects/new',
loadComponent: () => import('./pages/project-create/project-create.component').then(m => m.ProjectCreateComponent)
},
{
path: 'projects/:id',
loadComponent: () => import('./pages/project-details/project-details.component').then(m => m.ProjectDetailsComponent)
}
]
} }
]; ];

View file

@ -2,11 +2,12 @@ import { Component, signal, inject } from '@angular/core';
import { RouterOutlet } from '@angular/router'; import { RouterOutlet } from '@angular/router';
import { AuthService } from './services/auth.service'; import { AuthService } from './services/auth.service';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Navbar } from './navbar/navbar'; import { NavbarComponent } from './components/navbar/navbar.component';
import { FooterComponent } from './components/footer/footer.component';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
imports: [RouterOutlet, CommonModule, Navbar], imports: [RouterOutlet, CommonModule, NavbarComponent, FooterComponent],
templateUrl: './app.html', templateUrl: './app.html',
styleUrl: './app.css' styleUrl: './app.css'
}) })

View file

@ -15,33 +15,24 @@ export const httpInterceptor: HttpInterceptorFn = (req, next) => {
// Clone the request to add withCredentials flag // Clone the request to add withCredentials flag
// This ensures cookies are sent with every request // This ensures cookies are sent with every request
const reqWithCredentials = req.clone({ const reqWithCredentials = req.clone({
withCredentials: true withCredentials: true,
}); });
// Pass the cloned request to the next handler // Pass the cloned request to the next handler
return next(reqWithCredentials).pipe( return next(reqWithCredentials).pipe(
catchError((error: HttpErrorResponse) => { catchError((error: HttpErrorResponse) => {
// Handle different HTTP error codes
if (error.status === 401) { if (error.status === 401) {
// Unauthorized - redirect to login (but not for session check endpoint) if (router.url !== '/login') {
// Skip redirect for /me endpoint to avoid issues during app initialization
if (!req.url.endsWith('/me')) {
console.error('Unauthorized access - redirecting to login');
router.navigate(['/login']); router.navigate(['/login']);
} }
} else if (error.status === 403) { } else if (error.status === 403) {
// Forbidden
console.error('Access forbidden:', error.message); console.error('Access forbidden:', error.message);
} else if (error.status === 0) {
// Network error
console.error('Network error - check if the server is running');
} else { } else {
// Other errors
console.error(`HTTP Error ${error.status}:`, error.message); console.error(`HTTP Error ${error.status}:`, error.message);
} }
// Re-throw the error so components can handle it if needed // Re-throw the error so components can handle it if needed
return throwError(() => error); return throwError(() => error);
}) }),
); );
}; };

View file

@ -17,7 +17,7 @@ export interface ProjectFull {
export interface CreateProjectRequest { export interface CreateProjectRequest {
name: string; name: string;
description?: string; description: string;
} }
export interface UpdateProjectRequest { export interface UpdateProjectRequest {

View file

@ -26,7 +26,8 @@
name="description" name="description"
[(ngModel)]="description" [(ngModel)]="description"
rows="4" rows="4"
placeholder="Optional description" placeholder="Enter a project description"
required
></textarea> ></textarea>
</div> </div>

View file

@ -10,7 +10,7 @@ import { CreateProjectRequest, Project } from '../../models/projects.models';
standalone: true, standalone: true,
imports: [CommonModule, FormsModule], imports: [CommonModule, FormsModule],
templateUrl: './project-create.component.html', templateUrl: './project-create.component.html',
styleUrl: './project-create.component.css' styleUrl: './project-create.component.css',
}) })
export class ProjectCreateComponent { export class ProjectCreateComponent {
private apiService = inject(ApiService); private apiService = inject(ApiService);
@ -27,9 +27,14 @@ export class ProjectCreateComponent {
return; return;
} }
if (!this.description.trim()) {
this.errorMessage.set('Project description is required.');
return;
}
const payload: CreateProjectRequest = { const payload: CreateProjectRequest = {
name: this.name.trim(), name: this.name.trim(),
description: this.description.trim() || undefined description: this.description.trim(),
}; };
this.isSaving.set(true); this.isSaving.set(true);
@ -39,7 +44,7 @@ export class ProjectCreateComponent {
next: (project) => { next: (project) => {
this.isSaving.set(false); this.isSaving.set(false);
if (project?.id != null) { if (project?.id != null) {
this.router.navigate(['/projects', project.id]); this.router.navigate(['/projects/', project.id]);
} else { } else {
this.router.navigate(['/']); this.router.navigate(['/']);
} }
@ -47,9 +52,9 @@ export class ProjectCreateComponent {
error: (error) => { error: (error) => {
this.isSaving.set(false); this.isSaving.set(false);
this.errorMessage.set( this.errorMessage.set(
error?.error?.message || 'Failed to create project. Please try again.' error?.error?.message || 'Failed to create project. Please try again.',
); );
} },
}); });
} }

View file

@ -1,6 +1,6 @@
import { Injectable, inject, signal, computed } from '@angular/core'; import { Injectable, inject, signal, computed } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Observable, tap } from 'rxjs'; import { Observable, tap, catchError, throwError } from 'rxjs';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { LoginRequest, LoginResponse, User, AuthState } from '../models/auth.models'; import { LoginRequest, LoginResponse, User, AuthState } from '../models/auth.models';
import { environment } from '../config/environment'; import { environment } from '../config/environment';
@ -36,11 +36,11 @@ export class AuthService {
credentials credentials
).pipe( ).pipe(
tap(response => { tap(response => {
if (response.success && response.user) { if (response.success || response.user) {
// Update auth state // Update auth state even if the backend only sets a cookie
this.authState.set({ this.authState.set({
isAuthenticated: true, isAuthenticated: true,
user: response.user user: response.user ?? null
}); });
} }
}) })
@ -52,7 +52,7 @@ export class AuthService {
* Clears the session cookie on the backend * Clears the session cookie on the backend
*/ */
logout(): Observable<any> { logout(): Observable<any> {
return this.http.post(`${environment.apiBaseUrl}/me/logout`, {}).pipe( return this.http.get(`${environment.apiBaseUrl}/me/logout`).pipe(
tap(() => { tap(() => {
// Clear auth state // Clear auth state
this.authState.set({ this.authState.set({
@ -61,6 +61,11 @@ export class AuthService {
}); });
// Redirect to login // Redirect to login
this.router.navigate(['/login']); this.router.navigate(['/login']);
}),
catchError((error) => {
// Even if logout fails on backend, clear local state
this.clearAuthState();
return throwError(() => error);
}) })
); );
} }
@ -88,5 +93,6 @@ export class AuthService {
isAuthenticated: false, isAuthenticated: false,
user: null user: null
}); });
this.router.navigate(['/login']);
} }
} }