# 🏢 Multi-tenancy Implementation - PraFrota Frontend ## Overview This document outlines how the PraFrota frontend implements multi-tenancy to align with our backend architecture, ensuring proper tenant isolation and data management. ## 🏗️ Tenant Context ### Tenant Service ```typescript @Injectable({ providedIn: 'root' }) export class TenantService { private currentTenant = new BehaviorSubject(null); constructor(private http: HttpClient) {} // Get current tenant getCurrentTenant(): Observable { return this.currentTenant.asObservable(); } // Set current tenant setCurrentTenant(tenant: Tenant): void { this.currentTenant.next(tenant); } // Load tenant data loadTenantData(tenantId: string): Observable { return this.http.get>(`/api/tenant/${tenantId}`).pipe( map(response => response.data), tap(tenant => this.setCurrentTenant(tenant)) ); } } ``` ### Tenant Interface ```typescript interface Tenant { id: string; name: string; settings: TenantSettings; features: string[]; permissions: string[]; } ``` ## 🔐 Authentication & Authorization ### Auth Interceptor ```typescript @Injectable() export class TenantAuthInterceptor implements HttpInterceptor { constructor(private tenantService: TenantService) {} intercept(req: HttpRequest, next: HttpHandler): Observable> { return this.tenantService.getCurrentTenant().pipe( take(1), switchMap(tenant => { if (!tenant) { return next.handle(req); } const modifiedReq = req.clone({ setHeaders: { 'tenant-uuid': tenant.id, 'tenant-user-auth': this.getAuthToken() } }); return next.handle(modifiedReq); }) ); } private getAuthToken(): string { // Get token from secure storage return localStorage.getItem('auth_token') || ''; } } ``` ## 📦 Data Isolation ### Tenant-specific Storage ```typescript @Injectable({ providedIn: 'root' }) export class TenantStorageService { private readonly TENANT_PREFIX = 'tenant_'; constructor(private tenantService: TenantService) {} // Store tenant-specific data setItem(key: string, value: any): void { this.tenantService.getCurrentTenant().pipe( take(1) ).subscribe(tenant => { if (tenant) { const tenantKey = `${this.TENANT_PREFIX}${tenant.id}_${key}`; localStorage.setItem(tenantKey, JSON.stringify(value)); } }); } // Get tenant-specific data getItem(key: string): Observable { return this.tenantService.getCurrentTenant().pipe( take(1), map(tenant => { if (!tenant) return null; const tenantKey = `${this.TENANT_PREFIX}${tenant.id}_${key}`; const value = localStorage.getItem(tenantKey); return value ? JSON.parse(value) : null; }) ); } } ``` ## 🔄 Tenant Switching ### Tenant Switch Component ```typescript @Component({ selector: 'app-tenant-switch', standalone: true, template: `
` }) export class TenantSwitchComponent { tenantControl = new FormControl(''); tenants: Tenant[] = []; constructor( private tenantService: TenantService, private router: Router ) {} onTenantChange(): void { const tenantId = this.tenantControl.value; if (tenantId) { this.tenantService.loadTenantData(tenantId).subscribe(() => { // Clear tenant-specific data this.clearTenantData(); // Reload current route this.router.navigateByUrl(this.router.url); }); } } private clearTenantData(): void { // Clear tenant-specific data from storage // Clear tenant-specific state // Clear tenant-specific cache } } ``` ## 📊 Tenant-specific Features ### Feature Flags ```typescript @Injectable({ providedIn: 'root' }) export class FeatureFlagService { constructor(private tenantService: TenantService) {} isFeatureEnabled(feature: string): Observable { return this.tenantService.getCurrentTenant().pipe( map(tenant => tenant?.features.includes(feature) ?? false) ); } } ``` ### Feature Guard ```typescript @Injectable({ providedIn: 'root' }) export class FeatureGuard implements CanActivate { constructor( private featureFlagService: FeatureFlagService, private router: Router ) {} canActivate(route: ActivatedRouteSnapshot): Observable { const requiredFeature = route.data['requiredFeature']; return this.featureFlagService.isFeatureEnabled(requiredFeature).pipe( tap(enabled => { if (!enabled) { this.router.navigate(['/unauthorized']); } }) ); } } ``` ## 📝 Best Practices 1. **Data Isolation**: - Always use tenant-specific storage - Clear tenant data on switch - Validate tenant context 2. **Security**: - Always include tenant headers - Validate tenant permissions - Handle unauthorized access 3. **Performance**: - Cache tenant-specific data - Optimize tenant switching - Handle offline scenarios 4. **User Experience**: - Show tenant context - Handle tenant switching gracefully - Provide clear feedback ## 🔄 Implementation Checklist When implementing tenant-specific features: 1. [ ] Add tenant headers to requests 2. [ ] Implement tenant storage 3. [ ] Add feature flags 4. [ ] Implement tenant switching 5. [ ] Add proper validation 6. [ ] Handle errors appropriately 7. [ ] Test all scenarios ## 📚 Additional Resources - Backend Multi-tenancy: `/docs/mcp/MULTI_TENANCY.md` - Frontend Architecture: `/docs/ARCHITECTURE.md` - Authentication Guide: `/docs/AUTHENTICATION.md`