v1.4: Segurança multi-tenant, file serving via API e UX humanizada
- Validação cross-tenant no login e rotas protegidas
- File serving via /api/files/{bucket}/{path} (eliminação DNS)
- Mensagens de erro humanizadas inline (sem pop-ups)
- Middleware tenant detection via headers customizados
- Upload de logos retorna URLs via API
- README atualizado com changelog v1.4 completo
This commit is contained in:
79
front-end-agency/lib/server-api.ts
Normal file
79
front-end-agency/lib/server-api.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Server-side API functions
|
||||
* Estas funções são executadas APENAS no servidor (não no cliente)
|
||||
*/
|
||||
|
||||
import { cookies, headers } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_INTERNAL_URL || 'http://backend:8080';
|
||||
|
||||
interface AgencyBrandingData {
|
||||
logo_url?: string;
|
||||
primary_color?: string;
|
||||
secondary_color?: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Busca os dados de branding da agência no servidor
|
||||
* Usa o subdomínio do request para identificar a agência
|
||||
*/
|
||||
export async function getAgencyBranding(): Promise<AgencyBrandingData | null> {
|
||||
try {
|
||||
// Pegar o hostname do request
|
||||
const headersList = await headers();
|
||||
const hostname = headersList.get('host') || '';
|
||||
const subdomain = hostname.split('.')[0];
|
||||
|
||||
if (!subdomain || subdomain === 'localhost' || subdomain === 'www') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Buscar dados da agência pela API
|
||||
const url = `${API_BASE_URL}/api/tenant/config?subdomain=${subdomain}`;
|
||||
console.log(`[ServerAPI] Fetching agency config from: ${url}`);
|
||||
|
||||
const response = await fetch(url, {
|
||||
cache: 'no-store', // Sempre buscar dados atualizados
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`[ServerAPI] Failed to fetch agency branding for ${subdomain}: ${response.status}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log(`[ServerAPI] Agency branding data for ${subdomain}:`, JSON.stringify(data));
|
||||
return data as AgencyBrandingData;
|
||||
} catch (error) {
|
||||
console.error('[ServerAPI] Error fetching agency branding:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Busca apenas o logo da agência (para metadata)
|
||||
*/
|
||||
export async function getAgencyLogo(): Promise<string | null> {
|
||||
const branding = await getAgencyBranding();
|
||||
return branding?.logo_url || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Busca as cores da agência (para passar ao client component)
|
||||
*/
|
||||
export async function getAgencyColors(): Promise<{ primary: string; secondary: string } | null> {
|
||||
const branding = await getAgencyBranding();
|
||||
|
||||
if (branding?.primary_color && branding?.secondary_color) {
|
||||
return {
|
||||
primary: branding.primary_color,
|
||||
secondary: branding.secondary_color,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user